From 4b693240e099f4a5d46244dbaf0b7d792f51e326 Mon Sep 17 00:00:00 2001 From: f4ilji Date: Tue, 5 Nov 2024 15:08:08 +0500 Subject: [PATCH] Changes --- .idea/ntspi-new.iml | 8 +- .idea/php.xml | 7 + _docker/nginx/conf.d/nginx.conf | 40 +- app/Console/Commands/GenerateSitemap.php | 97 ++++ .../Pages/CreateAcademicJournal.php | 25 +- .../Pages/EditAcademicJournal.php | 24 +- .../Pages/CreateAdditionalEducation.php | 39 +- .../Pages/EditAdditionalEducation.php | 66 ++- app/Filament/Resources/DepartmentResource.php | 425 +++++++++++++++- .../Pages/CreateDepartment.php | 26 +- .../Pages/EditDepartment.php | 24 +- .../DivisionResource/Pages/CreateDivision.php | 9 +- .../DivisionResource/Pages/EditDivision.php | 24 +- .../Pages/CreateEducationalProgram.php | 26 +- .../Pages/EditEducationalProgram.php | 28 +- .../DepartmentsRelationManager.php | 410 +++++++++++++++- app/Filament/Resources/FacultyResource.php | 415 +++++++++++++++- .../FacultyResource/Pages/CreateFaculty.php | 25 +- .../FacultyResource/Pages/EditFaculty.php | 24 +- .../Pages/CreateLibraryNews.php | 25 +- .../Pages/EditLibraryNews.php | 25 +- .../Pages/CreateVirtualExhibition.php | 25 +- .../Pages/EditVirtualExhibition.php | 8 +- .../ClientAdditionalEducationController.php | 4 +- .../ClientDepartmentController.php | 4 +- .../Controllers/ClientDivisionController.php | 3 +- .../Controllers/ClientFacultyController.php | 4 +- .../Controllers/ClientProgramController.php | 4 +- .../Controllers/GenerateSitemapController.php | 63 +++ app/Http/Controllers/SearchController.php | 4 + app/Providers/AppServiceProvider.php | 2 +- composer.json | 1 + composer.lock | 457 +++++++++++++++++- config/sitemap.php | 57 +++ .../Client/Additional-educations/Show.vue | 17 +- .../js/Pages/Client/Departments/Show.vue | 18 +- resources/js/Pages/Client/Divisions/Show.vue | 15 +- resources/js/Pages/Client/Faculties/Show.vue | 16 +- resources/js/Pages/Client/Posts/Show.vue | 1 + resources/js/Pages/Client/Programs/Show.vue | 13 +- routes/web.php | 11 +- 41 files changed, 2106 insertions(+), 413 deletions(-) create mode 100644 app/Console/Commands/GenerateSitemap.php create mode 100644 app/Http/Controllers/GenerateSitemapController.php create mode 100644 config/sitemap.php diff --git a/.idea/ntspi-new.iml b/.idea/ntspi-new.iml index 9505dc4..6aafdfe 100644 --- a/.idea/ntspi-new.iml +++ b/.idea/ntspi-new.iml @@ -6,7 +6,6 @@ - @@ -173,6 +172,13 @@ + + + + + + + diff --git a/.idea/php.xml b/.idea/php.xml index 0769cbd..39687c1 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -183,6 +183,13 @@ + + + + + + + diff --git a/_docker/nginx/conf.d/nginx.conf b/_docker/nginx/conf.d/nginx.conf index 8bb2a65..22767be 100644 --- a/_docker/nginx/conf.d/nginx.conf +++ b/_docker/nginx/conf.d/nginx.conf @@ -1,32 +1,42 @@ server { - client_max_body_size 200M; - large_client_header_buffers 4 128k; - + client_max_body_size 200M; # Максимальный размер тела запроса + large_client_header_buffers 4 128k; # Размеры буферов для заголовков root /var/www/public; + location / { - add_header Access-Control-Allow-Origin *; - try_files $uri /index.php?$args; + add_header Access-Control-Allow-Origin *; # Заголовок для CORS + try_files $uri /index.php?$args; # Обработка запросов } location /sveden/ { alias /var/www/public/sveden/; index index.html; - try_files $uri $uri/ /sveden/index.html; + try_files $uri $uri/ /sveden/index.html; # Обработка статических файлов } - - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass app:9000; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; + try_files $uri =404; # Если файл не найден, возвращаем 404 + fastcgi_split_path_info ^(.+\.php)(/.+)$; # Разделение пути + fastcgi_pass app:9000; # Указываем сервер PHP-FPM + fastcgi_index index.php; # Индексный файл + include fastcgi_params; # Включаем параметры FastCGI + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # Путь к скрипту + fastcgi_param PATH_INFO $fastcgi_path_info; # Информация о пути + fastcgi_buffers 16 16k; # Увеличение буферов FastCGI + fastcgi_buffer_size 32k; # Размер буфера FastCGI } + # Защита от доступа к конфиденциальным файлам + location ~ /\.ht { + deny all; # Запрет доступа к файлам .htaccess + } + + # Настройки кэширования для статических файлов + location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|otf)$ { + expires 30d; # Кэширование на 30 дней + add_header Cache-Control "public, no-transform"; # Заголовок кэширования + } } diff --git a/app/Console/Commands/GenerateSitemap.php b/app/Console/Commands/GenerateSitemap.php new file mode 100644 index 0000000..bfe61d8 --- /dev/null +++ b/app/Console/Commands/GenerateSitemap.php @@ -0,0 +1,97 @@ +generatePages($sitemap); + $this->generatePosts($sitemap); + $this->generateEvents($sitemap); + + $sitemap->writeToFile(public_path('sitemap.xml')); + } + + protected function generatePages(Sitemap $sitemap) + { + $pages = Page::query() + ->where('is_visible', true) + ->where('code', 200) + ->get(); + + $this->addUrlsToSitemap($sitemap, $pages, function($page) { + return [ + 'path' => "/{$page->path}", + 'lastModificationDate' => $page->updated_at, + 'priority' => 0.5, + ]; + }); + } + + protected function generateEvents(Sitemap $sitemap) + { + $events = Event::query() + ->get(); + + $this->addUrlsToSitemap($sitemap, $events, function($event) { + return [ + 'path' => "/events/{$event->slug}", + 'lastModificationDate' => $event->updated_at, + 'priority' => 0.5, + ]; + }); + } + + protected function generatePosts(Sitemap $sitemap) + { + $posts = Post::query() + ->where('status', PostStatus::PUBLISHED) + ->get(); + + $this->addUrlsToSitemap($sitemap, $posts, function($post) { + return [ + 'path' => "/news/{$post->slug}", + 'lastModificationDate' => $post->updated_at, + 'priority' => 0.5, + ]; + }); + } + + protected function addUrlsToSitemap(Sitemap $sitemap, $items, callable $callback) + { + foreach ($items as $item) { + $urlData = $callback($item); + $sitemap->add(Url::create($urlData['path']) + ->setLastModificationDate($urlData['lastModificationDate']) + ->setPriority($urlData['priority'])); + } + } +} diff --git a/app/Filament/Resources/AcademicJournalResource/Pages/CreateAcademicJournal.php b/app/Filament/Resources/AcademicJournalResource/Pages/CreateAcademicJournal.php index 3231e5d..d126b2c 100644 --- a/app/Filament/Resources/AcademicJournalResource/Pages/CreateAcademicJournal.php +++ b/app/Filament/Resources/AcademicJournalResource/Pages/CreateAcademicJournal.php @@ -29,20 +29,15 @@ class CreateAcademicJournal extends CreateRecord private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['about_program']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['about_program']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['main_info']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; - } $image = ($data['preview'] !== null) ? $data['preview'] : null; - + } return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } @@ -68,22 +63,6 @@ class CreateAcademicJournal extends CreateRecord return $data; } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } private function getDataFromBlocks($block) : string { diff --git a/app/Filament/Resources/AcademicJournalResource/Pages/EditAcademicJournal.php b/app/Filament/Resources/AcademicJournalResource/Pages/EditAcademicJournal.php index 93f600e..4a19754 100644 --- a/app/Filament/Resources/AcademicJournalResource/Pages/EditAcademicJournal.php +++ b/app/Filament/Resources/AcademicJournalResource/Pages/EditAcademicJournal.php @@ -28,41 +28,19 @@ class EditAcademicJournal extends EditRecord } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['about_program']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['about_program']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['main_info']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; } - $image = ($this->record->preview !== null) ? $this->record->preview : null; - return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } diff --git a/app/Filament/Resources/AdditionalEducationResource/Pages/CreateAdditionalEducation.php b/app/Filament/Resources/AdditionalEducationResource/Pages/CreateAdditionalEducation.php index ad0a65e..e8615ed 100644 --- a/app/Filament/Resources/AdditionalEducationResource/Pages/CreateAdditionalEducation.php +++ b/app/Filament/Resources/AdditionalEducationResource/Pages/CreateAdditionalEducation.php @@ -11,10 +11,14 @@ class CreateAdditionalEducation extends CreateRecord { protected static string $resource = AdditionalEducationResource::class; + protected array $seoData; + protected function mutateFormDataBeforeCreate(array $data): array { $this->seoData = $this->generateSeo($data); + $data['search_data'] = $this->generateSearchData($data['content']); + return $data; } @@ -27,17 +31,16 @@ class CreateAdditionalEducation extends CreateRecord { $title = $data['title']; $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - $description = strip_tags($rowData['data']['content']); -// $image = ($data['preview'] !== null) ? $data['preview'] : null; - + if ($rowData !== null) { + $description = strip_tags($rowData['data']['content']); + } else { + $description = null; + } return [ 'title' => $title, - 'description' => Str::limit($description, 160), - 'image' => "", + 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), ]; } - - private function getFirstBlockByName(string $name, array $content) : array|null { $data = null; @@ -48,21 +51,17 @@ class CreateAdditionalEducation extends CreateRecord return $data; } - private function getBlockBySeoActiveState(string $name, array $content) : array|null + private function generateSearchData(array $data) : string { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } + $result = ""; + foreach ($data as $block) { + $result .= $this->getDataFromBlocks($block); } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; + // Удаляем лишние пробелы и переносы строк + $result = preg_replace('/\s+/', ' ', $result); + $result = trim($result); + + return strtolower($result); } private function getDataFromBlocks($block) : string diff --git a/app/Filament/Resources/AdditionalEducationResource/Pages/EditAdditionalEducation.php b/app/Filament/Resources/AdditionalEducationResource/Pages/EditAdditionalEducation.php index 7829d99..fae9c11 100644 --- a/app/Filament/Resources/AdditionalEducationResource/Pages/EditAdditionalEducation.php +++ b/app/Filament/Resources/AdditionalEducationResource/Pages/EditAdditionalEducation.php @@ -14,33 +14,37 @@ class EditAdditionalEducation extends EditRecord protected array $seoData; + protected function mutateFormDataBeforeSave(array $data): array { $this->seoData = $this->generateSeo($data); + $data['search_data'] = $this->generateSearchData($data['content']); + return $data; } protected function afterSave(): void { $this->record->seo()->update($this->seoData); + } private function generateSeo(array $data) : array { $title = $data['title']; $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - $description = strip_tags($rowData['data']['content']); -// $image = ($data['preview'] !== null) ? $data['preview'] : null; + if ($rowData !== null) { + $description = strip_tags($rowData['data']['content']); + } else { + $description = null; + } return [ 'title' => $title, - 'description' => Str::limit($description, 160), - 'image' => "", + 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), ]; } - - private function getFirstBlockByName(string $name, array $content) : array|null { $data = null; @@ -51,6 +55,56 @@ class EditAdditionalEducation extends EditRecord return $data; } + private function generateSearchData(array $data) : string + { + $result = ""; + foreach ($data as $block) { + $result .= $this->getDataFromBlocks($block); + } + // Удаляем лишние пробелы и переносы строк + $result = preg_replace('/\s+/', ' ', $result); + $result = trim($result); + + return strtolower($result); + } + + private function getDataFromBlocks($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; + } + protected function getHeaderActions(): array diff --git a/app/Filament/Resources/DepartmentResource.php b/app/Filament/Resources/DepartmentResource.php index 3537cb6..5fc20c9 100644 --- a/app/Filament/Resources/DepartmentResource.php +++ b/app/Filament/Resources/DepartmentResource.php @@ -2,25 +2,43 @@ namespace App\Filament\Resources; +use App\Enums\CustomFormStatus; +use App\Enums\PostStatus; use App\Filament\Resources\DepartmentResource\Pages; use App\Filament\Resources\DepartmentResource\RelationManagers; +use App\Helpers\ByteConverter; +use App\Models\Category; +use App\Models\CustomForm; use App\Models\Department; use App\Models\Faculty; +use App\Models\Page; +use App\Models\PageReferenceList; +use App\Models\Post; use Filament\Forms; +use Filament\Forms\Components\Builder; +use Filament\Forms\Components\FileUpload; +use Filament\Forms\Components\Hidden; +use Filament\Forms\Components\RichEditor; +use Filament\Forms\Components\Section; +use Filament\Forms\Components\Select; +use Filament\Forms\Components\Tabs; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; use Filament\Forms\Form; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Table; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; +use Illuminate\Support\Carbon; use Illuminate\Support\Str; +use Livewire\Features\SupportFileUploads\TemporaryUploadedFile; class DepartmentResource extends Resource { protected static ?string $model = Department::class; + protected static ?string $navigationGroup = 'Структура института'; + protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; protected static ?string $pluralLabel = 'Кафедры'; @@ -34,21 +52,402 @@ class DepartmentResource extends Resource { return $form ->schema([ - TextInput::make('title')->label('Название кафедры')->required() - ->live(onBlur: true) - ->afterStateUpdated(function (string $operation, $state, Forms\Set $set) { - $set('slug', Str::slug($state)); - }), - TextInput::make('slug')->label('Slug')->unique(ignoreRecord: true)->readOnly()->required(), - Forms\Components\Select::make('faculty_id') - ->options(Faculty::all()->pluck('title', 'id')) - ->label('Факультет') - ->required(), - Toggle::make('is_active')->default(true)->label('Активная кафедра')->inline(false), - + Section::make() + ->schema([ + Tabs::make('Tabs') + ->tabs([ + Tabs\Tab::make('Основная информация') + ->schema([ + TextInput::make('title')->label('Название факультета')->required() + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, string|null $state, Forms\Set $set) { + $set('slug', Str::slug($state)); + $set('seo.title', $state); + }), + TextInput::make('slug')->label('Slug')->unique(ignoreRecord: true)->readOnly()->required(), + Toggle::make('is_active')->default(true)->label('Активный факультет')->inline(false), + Forms\Components\Select::make('faculty_id') + ->options(Faculty::all()->pluck('title', 'id')) + ->label('Факультет') + ->required(), + ]), + Tabs\Tab::make('Описание факультета') + ->schema([ + Builder::make('content')->label('')->blocks([ + Builder\Block::make('heading')->label('Заголовок') + ->schema([ + TextInput::make('id')->hidden()->integer()->default(rand(2335235,324634264263426)), + TextInput::make('content') + ->label('') + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, string $state, Forms\Set $set, $get) { + }), + ]), + Builder\Block::make('paragraph') + ->schema([ + RichEditor::make('content') + ->toolbarButtons([ + 'blockquote', + 'bold', + 'bulletList', + 'italic', + 'link', + 'orderedList', + 'redo', + 'strike', + 'underline', + 'undo', + ]) + ->label(''), + ])->label('Текст'), + Builder\Block::make('files') + ->schema([ + Forms\Components\Repeater::make('file')->schema([ + Hidden::make('expansion')->required(), + Hidden::make('size')->required(), + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->getUploadedFileNameForStorageUsing( + fn (TemporaryUploadedFile $file): string => + str(Str::slug(pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME) . '-' . Carbon::now()->timestamp ) . '.' . $file->getClientOriginalExtension()) + ) + ->acceptedFileTypes([ + 'application/pdf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/zip' + ]) + ->maxSize(512000) + ->disk('public') + ->directory('files') + ->downloadable() + ->afterStateUpdated(function ($set, $state) { + $set('expansion', $state?->getClientOriginalExtension()); + $set('size', ByteConverter::bytesToHuman($state?->getSize())); + }) + ->visibility('public') + ]), + ]), + Builder\Block::make('person') + ->schema([ + TextInput::make('name') + ->label('Имя') + ->required() + ->maxLength(255), + FileUpload::make('photo') + ->label('Фотография') + ->image() + ->disk('public') + ->directory('images') + ->imageEditor(), + Forms\Components\Repeater::make('info')->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('column') + ->required() + ->maxLength(255), + TextInput::make('content') + ->required() + ->maxLength(255), + ]), + ])->minItems(1), + ]), + Builder\Block::make('stepper') + ->schema([ + TextInput::make('step_name') + ->label('Название шага') + ->required() + ->maxLength(255), + Forms\Components\Repeater::make('steps')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255)->columnSpanFull(), + RichEditor::make('content')->required(), + ])->minItems(1), + ]), + Builder\Block::make('tabs') + ->schema([ + Forms\Components\Repeater::make('tab')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255)->columnSpanFull(), + \Filament\Forms\Components\Builder::make('content')->label('')->blocks([ + Builder\Block::make('heading')->label('Заголовок') + ->schema([ + TextInput::make('id')->hidden()->integer()->default(rand(2335235,324634264263426)), + TextInput::make('content') + ->label('') + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, string $state, Forms\Set $set, $get) { + }), + ]), + Builder\Block::make('paragraph') + ->schema([ + RichEditor::make('content') + ->toolbarButtons([ + 'blockquote', + 'bold', + 'bulletList', + 'italic', + 'link', + 'orderedList', + 'redo', + 'strike', + 'underline', + 'undo', + ]) + ->label(''), + ])->label('Текст'), + Builder\Block::make('files') + ->schema([ + Forms\Components\Repeater::make('file')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->acceptedFileTypes([ + 'application/pdf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/zip' + ]) + ->maxSize(512000) + ->disk('public') + ->directory('files') + ->downloadable() + ->visibility('public') + ]), + ]), + Builder\Block::make('person') + ->schema([ + TextInput::make('name') + ->label('Имя') + ->required() + ->maxLength(255), + FileUpload::make('photo') + ->label('Фотография') + ->image() + ->disk('public') + ->directory('images') + ->imageEditor(), + Forms\Components\Repeater::make('info')->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('column') + ->required() + ->maxLength(255), + TextInput::make('content') + ->required() + ->maxLength(255), + ]), + ])->minItems(1), + ]), + Builder\Block::make('stepper') + ->schema([ + TextInput::make('step_name') + ->label('Название шага') + ->required() + ->maxLength(255), + Forms\Components\Repeater::make('steps')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255)->columnSpanFull(), + RichEditor::make('content')->required(), + ])->minItems(1), + ]), + Builder\Block::make('images') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Слайдер изображений'), + Builder\Block::make('image') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Изображение'), + Builder\Block::make('video') + ->schema([ + TextInput::make('mime')->readOnly(), + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->acceptedFileTypes([ + 'video/mp4', + 'video/quicktime', + 'video/x-msvideo', + 'video/x-ms-wmv', + 'video/avi', + 'video/webm', + 'video/ogg', + 'video/3gpp', + 'video/3gpp2', + 'video/x-m4v', + ]) + ->disk('public') + ->directory('videos') + ->afterStateUpdated(fn (callable $set, $state) => $set('mime', $state?->getMimeType())), + ]), + Builder\Block::make('postsList') + ->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('count') + ->label('Количество запией') + ->integer(), + Select::make('category') + ->options(Category::all()->pluck('title', 'id')) + ]), + ])->label('Список новостей'), + ]) + ->collapsed() + ->blockNumbers(false) + ->collapsible() + ->addActionLabel('Добавить новый блок'), + ])->minItems(1), + ]), + Builder\Block::make('images') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Слайдер изображений'), + Builder\Block::make('image') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Изображение'), + Builder\Block::make('video') + ->schema([ + TextInput::make('mime')->readOnly(), + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->acceptedFileTypes([ + 'video/mp4', + 'video/quicktime', + 'video/x-msvideo', + 'video/x-ms-wmv', + 'video/avi', + 'video/webm', + 'video/ogg', + 'video/3gpp', + 'video/3gpp2', + 'video/x-m4v', + ]) + ->disk('public') + ->directory('videos') + ->afterStateUpdated(fn (callable $set, $state) => $set('mime', $state?->getMimeType())), + ]), + Builder\Block::make('postsList') + ->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('count') + ->label('Количество запией') + ->integer(), + Select::make('category') + ->options(Category::all()->pluck('title', 'id')) + ]), + ])->label('Список новостей'), + Builder\Block::make('postItem') + ->schema([ + Select::make('post') + ->options(Post::query()->where('status', PostStatus::PUBLISHED)->pluck('title', 'id')) + ->searchable() + ->required(), + ])->label('Новость'), + Builder\Block::make('pageItem') + ->schema([ + Select::make('page') + ->options(Page::query()->where('title', '!=' , null)->where('is_visible', true)->pluck('title', 'id')) + ->searchable() + ->required(), + ])->label('Страница'), + Builder\Block::make('customForm') + ->schema([ + Select::make('form') + ->options(CustomForm::query()->where('status', CustomFormStatus::PUBLISHED)->pluck('title', 'form_id')) + ->searchable() + ->required(), + ])->label('Форма'), + Builder\Block::make('pageResourceList') + ->schema([ + Select::make('resource') + ->options(PageReferenceList::query()->where('is_active', true)->pluck('title', 'slug')) + ->searchable() + ->required(), + ])->label('Ресурсы'), + ]) + ->collapsed() + ->blockNumbers(false) + ->collapsible() + ->required() + ->blockPickerColumns(3) + ->blockPickerWidth('2xl') + ->addActionLabel('Добавить новый блок'), + ]), + ]), + ]) ]); + + } + + public static function table(Table $table): Table { return $table diff --git a/app/Filament/Resources/DepartmentResource/Pages/CreateDepartment.php b/app/Filament/Resources/DepartmentResource/Pages/CreateDepartment.php index a943b2d..299b5e3 100644 --- a/app/Filament/Resources/DepartmentResource/Pages/CreateDepartment.php +++ b/app/Filament/Resources/DepartmentResource/Pages/CreateDepartment.php @@ -13,7 +13,6 @@ class CreateDepartment extends CreateRecord protected array $seoData; - protected function mutateFormDataBeforeCreate(array $data): array { $this->seoData = $this->generateSeo($data); @@ -29,20 +28,15 @@ class CreateDepartment extends CreateRecord private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['content']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; - } $image = ($data['preview'] !== null) ? $data['preview'] : null; - + } return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } @@ -68,22 +62,6 @@ class CreateDepartment extends CreateRecord return $data; } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } private function getDataFromBlocks($block) : string { diff --git a/app/Filament/Resources/DepartmentResource/Pages/EditDepartment.php b/app/Filament/Resources/DepartmentResource/Pages/EditDepartment.php index e044bc0..5fb2f96 100644 --- a/app/Filament/Resources/DepartmentResource/Pages/EditDepartment.php +++ b/app/Filament/Resources/DepartmentResource/Pages/EditDepartment.php @@ -28,41 +28,19 @@ class EditDepartment extends EditRecord } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['content']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; } - $image = ($this->record->preview !== null) ? $this->record->preview : null; - return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } diff --git a/app/Filament/Resources/DivisionResource/Pages/CreateDivision.php b/app/Filament/Resources/DivisionResource/Pages/CreateDivision.php index 4502ff8..ba801e3 100644 --- a/app/Filament/Resources/DivisionResource/Pages/CreateDivision.php +++ b/app/Filament/Resources/DivisionResource/Pages/CreateDivision.php @@ -29,20 +29,15 @@ class CreateDivision extends CreateRecord private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['description']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['description']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['description']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; - } $image = ($data['preview'] !== null) ? $data['preview'] : null; - + } return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } diff --git a/app/Filament/Resources/DivisionResource/Pages/EditDivision.php b/app/Filament/Resources/DivisionResource/Pages/EditDivision.php index d8b2700..76ca95d 100644 --- a/app/Filament/Resources/DivisionResource/Pages/EditDivision.php +++ b/app/Filament/Resources/DivisionResource/Pages/EditDivision.php @@ -28,41 +28,19 @@ class EditDivision extends EditRecord } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['description']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['description']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['description']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; } - $image = ($this->record->preview !== null) ? $this->record->preview : null; - return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } diff --git a/app/Filament/Resources/EducationalProgramResource/Pages/CreateEducationalProgram.php b/app/Filament/Resources/EducationalProgramResource/Pages/CreateEducationalProgram.php index 6a80529..03bf770 100644 --- a/app/Filament/Resources/EducationalProgramResource/Pages/CreateEducationalProgram.php +++ b/app/Filament/Resources/EducationalProgramResource/Pages/CreateEducationalProgram.php @@ -33,20 +33,15 @@ class CreateEducationalProgram extends CreateRecord private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['about_program']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; - } $image = ($data['preview'] !== null) ? $data['preview'] : null; - + } return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } @@ -72,23 +67,6 @@ class CreateEducationalProgram extends CreateRecord return $data; } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } - private function getDataFromBlocks($block) : string { $data = ""; diff --git a/app/Filament/Resources/EducationalProgramResource/Pages/EditEducationalProgram.php b/app/Filament/Resources/EducationalProgramResource/Pages/EditEducationalProgram.php index fd61f9a..09ebc54 100644 --- a/app/Filament/Resources/EducationalProgramResource/Pages/EditEducationalProgram.php +++ b/app/Filament/Resources/EducationalProgramResource/Pages/EditEducationalProgram.php @@ -13,10 +13,8 @@ class EditEducationalProgram extends EditRecord { protected static string $resource = EducationalProgramResource::class; - protected array $seoData; - protected function mutateFormDataBeforeSave(array $data): array { $this->seoData = $this->generateSeo($data); @@ -30,42 +28,18 @@ class EditEducationalProgram extends EditRecord $this->record->seo()->update($this->seoData); } - - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } - private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['about_program']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; } - $image = ($this->record->preview !== null) ? $this->record->preview : null; - return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } diff --git a/app/Filament/Resources/FacultyRecourceResource/RelationManagers/DepartmentsRelationManager.php b/app/Filament/Resources/FacultyRecourceResource/RelationManagers/DepartmentsRelationManager.php index b6c2ad3..5205dce 100644 --- a/app/Filament/Resources/FacultyRecourceResource/RelationManagers/DepartmentsRelationManager.php +++ b/app/Filament/Resources/FacultyRecourceResource/RelationManagers/DepartmentsRelationManager.php @@ -2,17 +2,33 @@ namespace App\Filament\Resources\FacultyRecourceResource\RelationManagers; +use App\Enums\CustomFormStatus; +use App\Enums\PostStatus; +use App\Helpers\ByteConverter; +use App\Models\Category; +use App\Models\CustomForm; use App\Models\Faculty; +use App\Models\Page; +use App\Models\PageReferenceList; +use App\Models\Post; use Filament\Forms; +use Filament\Forms\Components\Builder; +use Filament\Forms\Components\FileUpload; +use Filament\Forms\Components\Hidden; +use Filament\Forms\Components\RichEditor; +use Filament\Forms\Components\Section; +use Filament\Forms\Components\Select; +use Filament\Forms\Components\Tabs; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; use Filament\Forms\Form; use Filament\Resources\RelationManagers\RelationManager; use Filament\Tables; use Filament\Tables\Table; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; +use Illuminate\Support\Carbon; use Illuminate\Support\Str; +use Livewire\Features\SupportFileUploads\TemporaryUploadedFile; class DepartmentsRelationManager extends RelationManager { @@ -25,13 +41,391 @@ class DepartmentsRelationManager extends RelationManager { return $form ->schema([ - TextInput::make('title')->label('Название кафедры')->required() - ->live(onBlur: true) - ->afterStateUpdated(function (string $operation, $state, Forms\Set $set) { - $set('slug', Str::slug($state)); - }), - TextInput::make('slug')->label('Slug')->unique(ignoreRecord: true)->readOnly()->required(), - Toggle::make('is_active')->default(true)->label('Активная кафедра')->inline(false), + Section::make() + ->schema([ + Tabs::make('Tabs') + ->tabs([ + Tabs\Tab::make('Основная информация') + ->schema([ + TextInput::make('title')->label('Название факультета')->required() + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, string|null $state, Forms\Set $set) { + $set('slug', Str::slug($state)); + $set('seo.title', $state); + }), + TextInput::make('slug')->label('Slug')->unique(ignoreRecord: true)->readOnly()->required(), + Toggle::make('is_active')->default(true)->label('Активный факультет')->inline(false), + ]), + Tabs\Tab::make('Описание факультета') + ->schema([ + Builder::make('content')->label('')->blocks([ + Builder\Block::make('heading')->label('Заголовок') + ->schema([ + TextInput::make('id')->hidden()->integer()->default(rand(2335235,324634264263426)), + TextInput::make('content') + ->label('') + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, string $state, Forms\Set $set, $get) { + }), + ]), + Builder\Block::make('paragraph') + ->schema([ + RichEditor::make('content') + ->toolbarButtons([ + 'blockquote', + 'bold', + 'bulletList', + 'italic', + 'link', + 'orderedList', + 'redo', + 'strike', + 'underline', + 'undo', + ]) + ->label(''), + ])->label('Текст'), + Builder\Block::make('files') + ->schema([ + Forms\Components\Repeater::make('file')->schema([ + Hidden::make('expansion')->required(), + Hidden::make('size')->required(), + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->getUploadedFileNameForStorageUsing( + fn (TemporaryUploadedFile $file): string => + str(Str::slug(pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME) . '-' . Carbon::now()->timestamp ) . '.' . $file->getClientOriginalExtension()) + ) + ->acceptedFileTypes([ + 'application/pdf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/zip' + ]) + ->maxSize(512000) + ->disk('public') + ->directory('files') + ->downloadable() + ->afterStateUpdated(function ($set, $state) { + $set('expansion', $state?->getClientOriginalExtension()); + $set('size', ByteConverter::bytesToHuman($state?->getSize())); + }) + ->visibility('public') + ]), + ]), + Builder\Block::make('person') + ->schema([ + TextInput::make('name') + ->label('Имя') + ->required() + ->maxLength(255), + FileUpload::make('photo') + ->label('Фотография') + ->image() + ->disk('public') + ->directory('images') + ->imageEditor(), + Forms\Components\Repeater::make('info')->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('column') + ->required() + ->maxLength(255), + TextInput::make('content') + ->required() + ->maxLength(255), + ]), + ])->minItems(1), + ]), + Builder\Block::make('stepper') + ->schema([ + TextInput::make('step_name') + ->label('Название шага') + ->required() + ->maxLength(255), + Forms\Components\Repeater::make('steps')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255)->columnSpanFull(), + RichEditor::make('content')->required(), + ])->minItems(1), + ]), + Builder\Block::make('tabs') + ->schema([ + Forms\Components\Repeater::make('tab')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255)->columnSpanFull(), + \Filament\Forms\Components\Builder::make('content')->label('')->blocks([ + Builder\Block::make('heading')->label('Заголовок') + ->schema([ + TextInput::make('id')->hidden()->integer()->default(rand(2335235,324634264263426)), + TextInput::make('content') + ->label('') + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, string $state, Forms\Set $set, $get) { + }), + ]), + Builder\Block::make('paragraph') + ->schema([ + RichEditor::make('content') + ->toolbarButtons([ + 'blockquote', + 'bold', + 'bulletList', + 'italic', + 'link', + 'orderedList', + 'redo', + 'strike', + 'underline', + 'undo', + ]) + ->label(''), + ])->label('Текст'), + Builder\Block::make('files') + ->schema([ + Forms\Components\Repeater::make('file')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->acceptedFileTypes([ + 'application/pdf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/zip' + ]) + ->maxSize(512000) + ->disk('public') + ->directory('files') + ->downloadable() + ->visibility('public') + ]), + ]), + Builder\Block::make('person') + ->schema([ + TextInput::make('name') + ->label('Имя') + ->required() + ->maxLength(255), + FileUpload::make('photo') + ->label('Фотография') + ->image() + ->disk('public') + ->directory('images') + ->imageEditor(), + Forms\Components\Repeater::make('info')->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('column') + ->required() + ->maxLength(255), + TextInput::make('content') + ->required() + ->maxLength(255), + ]), + ])->minItems(1), + ]), + Builder\Block::make('stepper') + ->schema([ + TextInput::make('step_name') + ->label('Название шага') + ->required() + ->maxLength(255), + Forms\Components\Repeater::make('steps')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255)->columnSpanFull(), + RichEditor::make('content')->required(), + ])->minItems(1), + ]), + Builder\Block::make('images') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Слайдер изображений'), + Builder\Block::make('image') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Изображение'), + Builder\Block::make('video') + ->schema([ + TextInput::make('mime')->readOnly(), + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->acceptedFileTypes([ + 'video/mp4', + 'video/quicktime', + 'video/x-msvideo', + 'video/x-ms-wmv', + 'video/avi', + 'video/webm', + 'video/ogg', + 'video/3gpp', + 'video/3gpp2', + 'video/x-m4v', + ]) + ->disk('public') + ->directory('videos') + ->afterStateUpdated(fn (callable $set, $state) => $set('mime', $state?->getMimeType())), + ]), + Builder\Block::make('postsList') + ->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('count') + ->label('Количество запией') + ->integer(), + Select::make('category') + ->options(Category::all()->pluck('title', 'id')) + ]), + ])->label('Список новостей'), + ]) + ->collapsed() + ->blockNumbers(false) + ->collapsible() + ->addActionLabel('Добавить новый блок'), + ])->minItems(1), + ]), + Builder\Block::make('images') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Слайдер изображений'), + Builder\Block::make('image') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Изображение'), + Builder\Block::make('video') + ->schema([ + TextInput::make('mime')->readOnly(), + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->acceptedFileTypes([ + 'video/mp4', + 'video/quicktime', + 'video/x-msvideo', + 'video/x-ms-wmv', + 'video/avi', + 'video/webm', + 'video/ogg', + 'video/3gpp', + 'video/3gpp2', + 'video/x-m4v', + ]) + ->disk('public') + ->directory('videos') + ->afterStateUpdated(fn (callable $set, $state) => $set('mime', $state?->getMimeType())), + ]), + Builder\Block::make('postsList') + ->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('count') + ->label('Количество запией') + ->integer(), + Select::make('category') + ->options(Category::all()->pluck('title', 'id')) + ]), + ])->label('Список новостей'), + Builder\Block::make('postItem') + ->schema([ + Select::make('post') + ->options(Post::query()->where('status', PostStatus::PUBLISHED)->pluck('title', 'id')) + ->searchable() + ->required(), + ])->label('Новость'), + Builder\Block::make('pageItem') + ->schema([ + Select::make('page') + ->options(Page::query()->where('title', '!=' , null)->where('is_visible', true)->pluck('title', 'id')) + ->searchable() + ->required(), + ])->label('Страница'), + Builder\Block::make('customForm') + ->schema([ + Select::make('form') + ->options(CustomForm::query()->where('status', CustomFormStatus::PUBLISHED)->pluck('title', 'form_id')) + ->searchable() + ->required(), + ])->label('Форма'), + Builder\Block::make('pageResourceList') + ->schema([ + Select::make('resource') + ->options(PageReferenceList::query()->where('is_active', true)->pluck('title', 'slug')) + ->searchable() + ->required(), + ])->label('Ресурсы'), + ]) + ->collapsed() + ->blockNumbers(false) + ->collapsible() + ->required() + ->blockPickerColumns(3) + ->blockPickerWidth('2xl') + ->addActionLabel('Добавить новый блок'), + ]), + ]), + ]) ]); } diff --git a/app/Filament/Resources/FacultyResource.php b/app/Filament/Resources/FacultyResource.php index dd38640..0686944 100644 --- a/app/Filament/Resources/FacultyResource.php +++ b/app/Filament/Resources/FacultyResource.php @@ -2,21 +2,37 @@ namespace App\Filament\Resources; +use App\Enums\CustomFormStatus; +use App\Enums\PostStatus; use App\Filament\Resources\FacultyRecourceResource\RelationManagers\DepartmentsRelationManager; use App\Filament\Resources\FacultyResource\Pages; use App\Filament\Resources\FacultyResource\RelationManagers; use App\Filament\Resources\FacultyResource\RelationManagers\WorkersRelationManager; +use App\Helpers\ByteConverter; +use App\Models\Category; +use App\Models\CustomForm; use App\Models\Faculty; +use App\Models\Page; +use App\Models\PageReferenceList; +use App\Models\Post; use Filament\Forms; +use Filament\Forms\Components\Builder; +use Filament\Forms\Components\FileUpload; +use Filament\Forms\Components\Hidden; +use Filament\Forms\Components\RichEditor; +use Filament\Forms\Components\Section; +use Filament\Forms\Components\Select; +use Filament\Forms\Components\Tabs; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; use Filament\Forms\Form; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Table; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; +use Illuminate\Support\Carbon; use Illuminate\Support\Str; +use Livewire\Features\SupportFileUploads\TemporaryUploadedFile; class FacultyResource extends Resource { @@ -36,16 +52,392 @@ class FacultyResource extends Resource { return $form ->schema([ - TextInput::make('title')->label('Название факультета')->required(), - TextInput::make('abbreviation')->label('Аббревиатура')->required() - ->live(onBlur: true) - ->afterStateUpdated(function (string $operation, $state, Forms\Set $set) { - $set('slug', Str::slug($state)); - }), - TextInput::make('slug')->label('Slug')->unique(ignoreRecord: true)->readOnly()->required(), - Toggle::make('is_active')->default(true)->label('Активный факультет')->inline(false), - - ]); + Section::make() + ->schema([ + Tabs::make('Tabs') + ->tabs([ + Tabs\Tab::make('Основная информация') + ->schema([ + TextInput::make('title')->label('Название факультета')->required() + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, string|null $state, Forms\Set $set) { + $set('slug', Str::slug($state)); + $set('seo.title', $state); + }), + TextInput::make('slug')->label('Slug')->unique(ignoreRecord: true)->readOnly()->required(), + Toggle::make('is_active')->default(true)->label('Активный факультет')->inline(false), + ]), + Tabs\Tab::make('Описание факультета') + ->schema([ + Builder::make('content')->label('')->blocks([ + Builder\Block::make('heading')->label('Заголовок') + ->schema([ + TextInput::make('id')->hidden()->integer()->default(rand(2335235,324634264263426)), + TextInput::make('content') + ->label('') + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, string $state, Forms\Set $set, $get) { + }), + ]), + Builder\Block::make('paragraph') + ->schema([ + RichEditor::make('content') + ->toolbarButtons([ + 'blockquote', + 'bold', + 'bulletList', + 'italic', + 'link', + 'orderedList', + 'redo', + 'strike', + 'underline', + 'undo', + ]) + ->label(''), + ])->label('Текст'), + Builder\Block::make('files') + ->schema([ + Forms\Components\Repeater::make('file')->schema([ + Hidden::make('expansion')->required(), + Hidden::make('size')->required(), + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->getUploadedFileNameForStorageUsing( + fn (TemporaryUploadedFile $file): string => + str(Str::slug(pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME) . '-' . Carbon::now()->timestamp ) . '.' . $file->getClientOriginalExtension()) + ) + ->acceptedFileTypes([ + 'application/pdf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/zip' + ]) + ->maxSize(512000) + ->disk('public') + ->directory('files') + ->downloadable() + ->afterStateUpdated(function ($set, $state) { + $set('expansion', $state?->getClientOriginalExtension()); + $set('size', ByteConverter::bytesToHuman($state?->getSize())); + }) + ->visibility('public') + ]), + ]), + Builder\Block::make('person') + ->schema([ + TextInput::make('name') + ->label('Имя') + ->required() + ->maxLength(255), + FileUpload::make('photo') + ->label('Фотография') + ->image() + ->disk('public') + ->directory('images') + ->imageEditor(), + Forms\Components\Repeater::make('info')->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('column') + ->required() + ->maxLength(255), + TextInput::make('content') + ->required() + ->maxLength(255), + ]), + ])->minItems(1), + ]), + Builder\Block::make('stepper') + ->schema([ + TextInput::make('step_name') + ->label('Название шага') + ->required() + ->maxLength(255), + Forms\Components\Repeater::make('steps')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255)->columnSpanFull(), + RichEditor::make('content')->required(), + ])->minItems(1), + ]), + Builder\Block::make('tabs') + ->schema([ + Forms\Components\Repeater::make('tab')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255)->columnSpanFull(), + \Filament\Forms\Components\Builder::make('content')->label('')->blocks([ + Builder\Block::make('heading')->label('Заголовок') + ->schema([ + TextInput::make('id')->hidden()->integer()->default(rand(2335235,324634264263426)), + TextInput::make('content') + ->label('') + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, string $state, Forms\Set $set, $get) { + }), + ]), + Builder\Block::make('paragraph') + ->schema([ + RichEditor::make('content') + ->toolbarButtons([ + 'blockquote', + 'bold', + 'bulletList', + 'italic', + 'link', + 'orderedList', + 'redo', + 'strike', + 'underline', + 'undo', + ]) + ->label(''), + ])->label('Текст'), + Builder\Block::make('files') + ->schema([ + Forms\Components\Repeater::make('file')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->acceptedFileTypes([ + 'application/pdf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/zip' + ]) + ->maxSize(512000) + ->disk('public') + ->directory('files') + ->downloadable() + ->visibility('public') + ]), + ]), + Builder\Block::make('person') + ->schema([ + TextInput::make('name') + ->label('Имя') + ->required() + ->maxLength(255), + FileUpload::make('photo') + ->label('Фотография') + ->image() + ->disk('public') + ->directory('images') + ->imageEditor(), + Forms\Components\Repeater::make('info')->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('column') + ->required() + ->maxLength(255), + TextInput::make('content') + ->required() + ->maxLength(255), + ]), + ])->minItems(1), + ]), + Builder\Block::make('stepper') + ->schema([ + TextInput::make('step_name') + ->label('Название шага') + ->required() + ->maxLength(255), + Forms\Components\Repeater::make('steps')->schema([ + TextInput::make('title') + ->required() + ->maxLength(255)->columnSpanFull(), + RichEditor::make('content')->required(), + ])->minItems(1), + ]), + Builder\Block::make('images') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Слайдер изображений'), + Builder\Block::make('image') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Изображение'), + Builder\Block::make('video') + ->schema([ + TextInput::make('mime')->readOnly(), + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->acceptedFileTypes([ + 'video/mp4', + 'video/quicktime', + 'video/x-msvideo', + 'video/x-ms-wmv', + 'video/avi', + 'video/webm', + 'video/ogg', + 'video/3gpp', + 'video/3gpp2', + 'video/x-m4v', + ]) + ->disk('public') + ->directory('videos') + ->afterStateUpdated(fn (callable $set, $state) => $set('mime', $state?->getMimeType())), + ]), + Builder\Block::make('postsList') + ->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('count') + ->label('Количество запией') + ->integer(), + Select::make('category') + ->options(Category::all()->pluck('title', 'id')) + ]), + ])->label('Список новостей'), + ]) + ->collapsed() + ->blockNumbers(false) + ->collapsible() + ->addActionLabel('Добавить новый блок'), + ])->minItems(1), + ]), + Builder\Block::make('images') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Слайдер изображений'), + Builder\Block::make('image') + ->schema([ + FileUpload::make('url') + ->label('Изображение(-я)') + ->image() + ->multiple() + ->reorderable() + ->maxFiles(5) + ->disk('public') + ->directory('images') + ->imageEditor() + ->required(), + TextInput::make('alt') + ->label('Описание') + ->placeholder('Необязяательно') + ])->label('Изображение'), + Builder\Block::make('video') + ->schema([ + TextInput::make('mime')->readOnly(), + TextInput::make('title') + ->required() + ->maxLength(255) + ->autofocus(), + FileUpload::make('path') + ->required() + ->acceptedFileTypes([ + 'video/mp4', + 'video/quicktime', + 'video/x-msvideo', + 'video/x-ms-wmv', + 'video/avi', + 'video/webm', + 'video/ogg', + 'video/3gpp', + 'video/3gpp2', + 'video/x-m4v', + ]) + ->disk('public') + ->directory('videos') + ->afterStateUpdated(fn (callable $set, $state) => $set('mime', $state?->getMimeType())), + ]), + Builder\Block::make('postsList') + ->schema([ + Forms\Components\Grid::make(2)->schema([ + TextInput::make('count') + ->label('Количество запией') + ->integer(), + Select::make('category') + ->options(Category::all()->pluck('title', 'id')) + ]), + ])->label('Список новостей'), + Builder\Block::make('postItem') + ->schema([ + Select::make('post') + ->options(Post::query()->where('status', PostStatus::PUBLISHED)->pluck('title', 'id')) + ->searchable() + ->required(), + ])->label('Новость'), + Builder\Block::make('pageItem') + ->schema([ + Select::make('page') + ->options(Page::query()->where('title', '!=' , null)->where('is_visible', true)->pluck('title', 'id')) + ->searchable() + ->required(), + ])->label('Страница'), + Builder\Block::make('customForm') + ->schema([ + Select::make('form') + ->options(CustomForm::query()->where('status', CustomFormStatus::PUBLISHED)->pluck('title', 'form_id')) + ->searchable() + ->required(), + ])->label('Форма'), + Builder\Block::make('pageResourceList') + ->schema([ + Select::make('resource') + ->options(PageReferenceList::query()->where('is_active', true)->pluck('title', 'slug')) + ->searchable() + ->required(), + ])->label('Ресурсы'), + ]) + ->collapsed() + ->blockNumbers(false) + ->collapsible() + ->required() + ->blockPickerColumns(3) + ->blockPickerWidth('2xl') + ->addActionLabel('Добавить новый блок'), + ]), + ]), + ]) + ]); } public static function table(Table $table): Table @@ -70,7 +462,6 @@ class FacultyResource extends Resource public static function getRelations(): array { return [ - DepartmentsRelationManager::class, WorkersRelationManager::class ]; } diff --git a/app/Filament/Resources/FacultyResource/Pages/CreateFaculty.php b/app/Filament/Resources/FacultyResource/Pages/CreateFaculty.php index 825725b..466ba62 100644 --- a/app/Filament/Resources/FacultyResource/Pages/CreateFaculty.php +++ b/app/Filament/Resources/FacultyResource/Pages/CreateFaculty.php @@ -29,20 +29,15 @@ class CreateFaculty extends CreateRecord private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['content']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; - } $image = ($data['preview'] !== null) ? $data['preview'] : null; - + } return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } @@ -68,22 +63,6 @@ class CreateFaculty extends CreateRecord return $data; } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } private function getDataFromBlocks($block) : string { diff --git a/app/Filament/Resources/FacultyResource/Pages/EditFaculty.php b/app/Filament/Resources/FacultyResource/Pages/EditFaculty.php index f2560ce..85d7b3f 100644 --- a/app/Filament/Resources/FacultyResource/Pages/EditFaculty.php +++ b/app/Filament/Resources/FacultyResource/Pages/EditFaculty.php @@ -28,41 +28,19 @@ class EditFaculty extends EditRecord } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['content']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; } - $image = ($this->record->preview !== null) ? $this->record->preview : null; - return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } diff --git a/app/Filament/Resources/LibraryNewsResource/Pages/CreateLibraryNews.php b/app/Filament/Resources/LibraryNewsResource/Pages/CreateLibraryNews.php index d3e35cb..d2d382a 100644 --- a/app/Filament/Resources/LibraryNewsResource/Pages/CreateLibraryNews.php +++ b/app/Filament/Resources/LibraryNewsResource/Pages/CreateLibraryNews.php @@ -29,20 +29,15 @@ class CreateLibraryNews extends CreateRecord private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['content']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; - } $image = ($data['preview'] !== null) ? $data['preview'] : null; - + } return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } @@ -68,22 +63,6 @@ class CreateLibraryNews extends CreateRecord return $data; } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } private function getDataFromBlocks($block) : string { diff --git a/app/Filament/Resources/LibraryNewsResource/Pages/EditLibraryNews.php b/app/Filament/Resources/LibraryNewsResource/Pages/EditLibraryNews.php index 233675c..bfe1948 100644 --- a/app/Filament/Resources/LibraryNewsResource/Pages/EditLibraryNews.php +++ b/app/Filament/Resources/LibraryNewsResource/Pages/EditLibraryNews.php @@ -28,41 +28,18 @@ class EditLibraryNews extends EditRecord } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } - private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['content']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; } - $image = ($this->record->preview !== null) ? $this->record->preview : null; - return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } diff --git a/app/Filament/Resources/VirtualExhibitionResource/Pages/CreateVirtualExhibition.php b/app/Filament/Resources/VirtualExhibitionResource/Pages/CreateVirtualExhibition.php index 3d0f105..1b44900 100644 --- a/app/Filament/Resources/VirtualExhibitionResource/Pages/CreateVirtualExhibition.php +++ b/app/Filament/Resources/VirtualExhibitionResource/Pages/CreateVirtualExhibition.php @@ -29,20 +29,15 @@ class CreateVirtualExhibition extends CreateRecord private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['content']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; - } $image = ($data['preview'] !== null) ? $data['preview'] : null; - + } return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } @@ -68,22 +63,6 @@ class CreateVirtualExhibition extends CreateRecord return $data; } - private function getBlockBySeoActiveState(string $name, array $content) : array|null - { - $data = []; - foreach ($content as $block) { - if ($block['type'] === $name) { - $data[] = $block; - } - } - $block = null; - foreach ($data as $item) { - if ($item['data']['seo_active'] === true) { - $block = $item; - } - } - return $block; - } private function getDataFromBlocks($block) : string { diff --git a/app/Filament/Resources/VirtualExhibitionResource/Pages/EditVirtualExhibition.php b/app/Filament/Resources/VirtualExhibitionResource/Pages/EditVirtualExhibition.php index 9de0606..2a82a75 100644 --- a/app/Filament/Resources/VirtualExhibitionResource/Pages/EditVirtualExhibition.php +++ b/app/Filament/Resources/VirtualExhibitionResource/Pages/EditVirtualExhibition.php @@ -48,21 +48,15 @@ class EditVirtualExhibition extends EditRecord private function generateSeo(array $data) : array { $title = $data['title']; - $rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']); - if ($rowData === null) { - $rowData = $this->getFirstBlockByName('paragraph', $data['content']); - } + $rowData = $this->getFirstBlockByName('paragraph', $data['content']); if ($rowData !== null) { $description = strip_tags($rowData['data']['content']); } else { $description = null; } - $image = ($this->record->preview !== null) ? $this->record->preview : null; - return [ 'title' => $title, 'description' => Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160), - 'image' => $image, ]; } diff --git a/app/Http/Controllers/ClientAdditionalEducationController.php b/app/Http/Controllers/ClientAdditionalEducationController.php index 427bb2f..86c9580 100644 --- a/app/Http/Controllers/ClientAdditionalEducationController.php +++ b/app/Http/Controllers/ClientAdditionalEducationController.php @@ -134,6 +134,8 @@ class ClientAdditionalEducationController extends Controller $breadcrumbs = null; } - return Inertia::render('Client/Additional-educations/Show', compact('additionalEducation', 'breadcrumbs')); + $seo = $additionalEducation->seo; + + return Inertia::render('Client/Additional-educations/Show', compact('additionalEducation', 'breadcrumbs', 'seo')); } } diff --git a/app/Http/Controllers/ClientDepartmentController.php b/app/Http/Controllers/ClientDepartmentController.php index 061a4be..1a33b50 100644 --- a/app/Http/Controllers/ClientDepartmentController.php +++ b/app/Http/Controllers/ClientDepartmentController.php @@ -27,7 +27,9 @@ class ClientDepartmentController extends Controller ->with(['faculty', 'workers.userDetail', 'teachers.userDetail', 'programs.directionStudy']) ->first()); $directions = $this->groupProgramsByDirection($department->programs); - return Inertia::render('Client/Departments/Show', compact('department', 'departments', 'directions')); + + $seo = $department->seo; + return Inertia::render('Client/Departments/Show', compact('department', 'departments', 'directions', 'seo')); } diff --git a/app/Http/Controllers/ClientDivisionController.php b/app/Http/Controllers/ClientDivisionController.php index 127d434..4ecaa73 100644 --- a/app/Http/Controllers/ClientDivisionController.php +++ b/app/Http/Controllers/ClientDivisionController.php @@ -19,6 +19,7 @@ class ClientDivisionController extends Controller { $divisions = DivisionResource::collection(Division::query()->where('is_active', true)->get()); $division = new DivisionResource(Division::with('workers.userDetail')->where('is_active', true)->where('slug', $slug)->first()); - return Inertia::render('Client/Divisions/Show', compact('divisions', 'division')); + $seo = $division->seo; + return Inertia::render('Client/Divisions/Show', compact('divisions', 'division', 'seo')); } } diff --git a/app/Http/Controllers/ClientFacultyController.php b/app/Http/Controllers/ClientFacultyController.php index 8109738..e7f966d 100644 --- a/app/Http/Controllers/ClientFacultyController.php +++ b/app/Http/Controllers/ClientFacultyController.php @@ -16,10 +16,12 @@ class ClientFacultyController extends Controller return Inertia::render('Client/Faculties/Index', compact('faculties')); } + public function show(string $slug) { $faculties = FacultyResource::collection(Faculty::query()->where('is_active', true)->get()); $faculty = new FullFacultyResource(Faculty::where('slug', $slug)->where('is_active', true)->with(['departments.faculty', 'workers.userDetail'])->first()); - return Inertia::render('Client/Faculties/Show', compact('faculty', 'faculties')); + $seo = $faculty->seo; + return Inertia::render('Client/Faculties/Show', compact('faculty', 'faculties', 'seo')); } } diff --git a/app/Http/Controllers/ClientProgramController.php b/app/Http/Controllers/ClientProgramController.php index e9b3fab..9dccd2b 100644 --- a/app/Http/Controllers/ClientProgramController.php +++ b/app/Http/Controllers/ClientProgramController.php @@ -119,7 +119,9 @@ class ClientProgramController extends Controller $formsEdu = $formsEducational->mapWithKeys(function ($formEducational) { return [$formEducational->value => $formEducational->getLabel()]; }); - return Inertia::render('Client/Programs/Show', compact('program', 'formsEdu')); + + $seo = $this->seo; + return Inertia::render('Client/Programs/Show', compact('program', 'formsEdu', 'seo')); } private function getAdmissionCampaignName() : string diff --git a/app/Http/Controllers/GenerateSitemapController.php b/app/Http/Controllers/GenerateSitemapController.php new file mode 100644 index 0000000..e1052e1 --- /dev/null +++ b/app/Http/Controllers/GenerateSitemapController.php @@ -0,0 +1,63 @@ +generatePages($sitemap); + $this->generatePosts($sitemap); + + $sitemap->writeToFile(public_path('sitemap.xml')); + } + + protected function generatePages(Sitemap $sitemap) + { + $pages = Page::query() + ->where('is_visible', true) + ->where('code', 200) + ->get(); + + $this->addUrlsToSitemap($sitemap, $pages, function($page) { + return [ + 'path' => "/{$page->path}", + 'lastModificationDate' => $page->updated_at, + 'priority' => 0.5, + ]; + }); + } + + protected function generatePosts(Sitemap $sitemap) + { + $posts = Post::query() + ->where('status', PostStatus::PUBLISHED) + ->get(); + + $this->addUrlsToSitemap($sitemap, $posts, function($post) { + return [ + 'path' => "/news/{$post->slug}", + 'lastModificationDate' => $post->updated_at, + 'priority' => 0.5, + ]; + }); + } + + protected function addUrlsToSitemap(Sitemap $sitemap, $items, callable $callback) + { + foreach ($items as $item) { + $urlData = $callback($item); + $sitemap->add(Url::create($urlData['path']) + ->setLastModificationDate($urlData['lastModificationDate']) + ->setPriority($urlData['priority'])); + } + } +} diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 56d1516..7419c45 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -14,8 +14,10 @@ use App\Models\AdditionalEducation; use App\Models\EducationalGroup; use App\Models\EducationalProgram; use App\Models\Event; +use App\Models\Faculty; use App\Models\Page; use App\Models\Post; +use App\Models\User; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -43,6 +45,8 @@ class SearchController extends Controller ->add(AdditionalEducation::where('is_active', '=', true), 'title') ->add(EducationalGroup::with('schedules'), 'title') ->add(EducationalProgram::where('status', '=', true)->whereHas('admission_plans'), 'name') + ->add(Faculty::where('is_active', '=', true), 'title') + ->add(User::whereHas('userDetail'), ['name', 'userDetail.education', 'userDetail.awards', 'userDetail.publications',]) ->beginWithWildcard() ->orderByRelevance() ->includeModelType() diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 4b62667..eee458e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -31,7 +31,7 @@ class AppServiceProvider extends ServiceProvider setlocale(LC_TIME, 'ru_RU.UTF-8'); Carbon::setLocale(config('app.locale')); Model::preventLazyLoading(!app()->isProduction()); - URL::forceScheme('https'); +// URL::forceScheme('https'); self::registerFilamentNavigationGroups(); } diff --git a/composer.json b/composer.json index b4b8232..2b4f2e4 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "nesbot/carbon": "^2.71", "protonemedia/laravel-cross-eloquent-search": "^3.4", "pxlrbt/filament-excel": "^2.3", + "spatie/laravel-sitemap": "^7.2", "symfony/filesystem": "^6.3", "tightenco/ziggy": "^1.0", "vkcom/vk-php-sdk": "^5.131", diff --git a/composer.lock b/composer.lock index 22be470..ff7b492 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4ec8fd7d71eed92d6bd7cd1106725919", + "content-hash": "54c73bafbbc855a5a9df56d74018c728", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -4859,6 +4859,60 @@ }, "time": "2024-08-07T15:39:19+00:00" }, + { + "name": "nicmart/tree", + "version": "0.8.0", + "source": { + "type": "git", + "url": "https://github.com/nicmart/Tree.git", + "reference": "8d02952acc9779a2c14f7a9c4ac1650c3dacb545" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nicmart/Tree/zipball/8d02952acc9779a2c14f7a9c4ac1650c3dacb545", + "reference": "8d02952acc9779a2c14f7a9c4ac1650c3dacb545", + "shasum": "" + }, + "require": { + "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.31.0", + "ergebnis/license": "^2.4.0", + "ergebnis/php-cs-fixer-config": "^6.13.0", + "fakerphp/faker": "^1.23.0", + "infection/infection": "~0.26.19", + "phpunit/phpunit": "^9.6.14", + "psalm/plugin-phpunit": "~0.18.4", + "vimeo/psalm": "^5.16.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tree\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolò Martini", + "email": "nicmartnic@gmail.com" + }, + { + "name": "Andreas Möller", + "email": "am@localheinz.com" + } + ], + "description": "A basic but flexible php tree data structure and a fluent tree builder implementation.", + "support": { + "issues": "https://github.com/nicmart/Tree/issues", + "source": "https://github.com/nicmart/Tree/tree/0.8.0" + }, + "time": "2023-12-02T13:24:56+00:00" + }, { "name": "nikic/php-parser", "version": "v5.2.0", @@ -6329,6 +6383,74 @@ ], "time": "2022-12-17T21:53:22+00:00" }, + { + "name": "spatie/browsershot", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/browsershot.git", + "reference": "601f2758191d8c46b2ea587eea935a87da4f39e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/browsershot/zipball/601f2758191d8c46b2ea587eea935a87da4f39e8", + "reference": "601f2758191d8c46b2ea587eea935a87da4f39e8", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "ext-json": "*", + "php": "^8.2", + "spatie/temporary-directory": "^2.0", + "symfony/process": "^6.0|^7.0" + }, + "require-dev": { + "pestphp/pest": "^1.20", + "spatie/image": "^3.6", + "spatie/pdf-to-text": "^1.52", + "spatie/phpunit-snapshot-assertions": "^4.2.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Browsershot\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://github.com/freekmurze", + "role": "Developer" + } + ], + "description": "Convert a webpage to an image or pdf using headless Chrome", + "homepage": "https://github.com/spatie/browsershot", + "keywords": [ + "chrome", + "convert", + "headless", + "image", + "pdf", + "puppeteer", + "screenshot", + "webpage" + ], + "support": { + "source": "https://github.com/spatie/browsershot/tree/4.3.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-08-22T09:14:07+00:00" + }, { "name": "spatie/color", "version": "1.6.0", @@ -6388,6 +6510,74 @@ ], "time": "2024-09-20T14:00:15+00:00" }, + { + "name": "spatie/crawler", + "version": "8.2.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/crawler.git", + "reference": "c659f2fe4954249755990e42394a14d6d847a0a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/crawler/zipball/c659f2fe4954249755990e42394a14d6d847a0a7", + "reference": "c659f2fe4954249755990e42394a14d6d847a0a7", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.3", + "guzzlehttp/psr7": "^2.0", + "illuminate/collections": "^10.0|^11.0", + "nicmart/tree": "^0.8.0", + "php": "^8.1", + "spatie/browsershot": "^3.45|^4.0", + "spatie/robots-txt": "^2.0", + "symfony/dom-crawler": "^6.0|^7.0" + }, + "require-dev": { + "pestphp/pest": "^2.0", + "spatie/ray": "^1.37" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Crawler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be" + } + ], + "description": "Crawl all internal links found on a website", + "homepage": "https://github.com/spatie/crawler", + "keywords": [ + "crawler", + "link", + "spatie", + "website" + ], + "support": { + "issues": "https://github.com/spatie/crawler/issues", + "source": "https://github.com/spatie/crawler/tree/8.2.3" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-07-31T10:46:19+00:00" + }, { "name": "spatie/eloquent-sortable", "version": "4.4.0", @@ -6663,6 +6853,79 @@ ], "time": "2024-06-22T23:04:52+00:00" }, + { + "name": "spatie/laravel-sitemap", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-sitemap.git", + "reference": "6d3d7637690a9710456a01bacabf5088f93ffe11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-sitemap/zipball/6d3d7637690a9710456a01bacabf5088f93ffe11", + "reference": "6d3d7637690a9710456a01bacabf5088f93ffe11", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.8", + "illuminate/support": "^10.0|^11.0", + "nesbot/carbon": "^2.71|^3.0", + "php": "^8.2", + "spatie/crawler": "^8.0.1", + "spatie/laravel-package-tools": "^1.16.1", + "symfony/dom-crawler": "^6.3.4|^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.6.6", + "orchestra/testbench": "^8.14|^9.0", + "pestphp/pest": "^2.24", + "spatie/pest-plugin-snapshots": "^2.1", + "spatie/phpunit-snapshot-assertions": "^5.1.2", + "spatie/temporary-directory": "^2.2" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Sitemap\\SitemapServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Sitemap\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Create and generate sitemaps with ease", + "homepage": "https://github.com/spatie/laravel-sitemap", + "keywords": [ + "laravel-sitemap", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/laravel-sitemap/tree/7.2.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2024-05-21T12:31:34+00:00" + }, { "name": "spatie/laravel-tags", "version": "4.6.1", @@ -6815,6 +7078,66 @@ ], "time": "2024-07-24T14:26:27+00:00" }, + { + "name": "spatie/robots-txt", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/robots-txt.git", + "reference": "31763e5ca23fb0efa6dc6b700beb5ebfeab89432" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/robots-txt/zipball/31763e5ca23fb0efa6dc6b700beb5ebfeab89432", + "reference": "31763e5ca23fb0efa6dc6b700beb5ebfeab89432", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.0|^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Robots\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Determine if a page may be crawled from robots.txt and robots meta tags", + "homepage": "https://github.com/spatie/robots-txt", + "keywords": [ + "robots-txt", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/robots-txt/issues", + "source": "https://github.com/spatie/robots-txt/tree/2.2.3" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-10-22T08:17:03+00:00" + }, { "name": "spatie/shiki-php", "version": "2.0.0", @@ -6879,6 +7202,67 @@ ], "time": "2024-02-19T09:00:59+00:00" }, + { + "name": "spatie/temporary-directory", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/temporary-directory.git", + "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", + "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\TemporaryDirectory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily create, use and destroy temporary directories", + "homepage": "https://github.com/spatie/temporary-directory", + "keywords": [ + "php", + "spatie", + "temporary-directory" + ], + "support": { + "issues": "https://github.com/spatie/temporary-directory/issues", + "source": "https://github.com/spatie/temporary-directory/tree/2.2.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2023-12-25T11:46:58+00:00" + }, { "name": "symfony/console", "version": "v6.4.12", @@ -7105,6 +7489,73 @@ ], "time": "2024-04-18T09:32:20+00:00" }, + { + "name": "symfony/dom-crawler", + "version": "v7.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "794ddd5481ba15d8a04132c95e211cd5656e09fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/794ddd5481ba15d8a04132c95e211cd5656e09fb", + "reference": "794ddd5481ba15d8a04132c95e211cd5656e09fb", + "shasum": "" + }, + "require": { + "masterminds/html5": "^2.6", + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v7.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:11:02+00:00" + }, { "name": "symfony/error-handler", "version": "v6.4.10", @@ -11802,13 +12253,13 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.1", "ext-curl": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/config/sitemap.php b/config/sitemap.php new file mode 100644 index 0000000..69be0f3 --- /dev/null +++ b/config/sitemap.php @@ -0,0 +1,57 @@ + [ + + /* + * Whether or not cookies are used in a request. + */ + RequestOptions::COOKIES => true, + + /* + * The number of seconds to wait while trying to connect to a server. + * Use 0 to wait indefinitely. + */ + RequestOptions::CONNECT_TIMEOUT => 10, + + /* + * The timeout of the request in seconds. Use 0 to wait indefinitely. + */ + RequestOptions::TIMEOUT => 10, + + /* + * Describes the redirect behavior of a request. + */ + RequestOptions::ALLOW_REDIRECTS => false, + ], + + /* + * The sitemap generator can execute JavaScript on each page so it will + * discover links that are generated by your JS scripts. This feature + * is powered by headless Chrome. + */ + 'execute_javascript' => false, + + /* + * The package will make an educated guess as to where Google Chrome is installed. + * You can also manually pass its location here. + */ + 'chrome_binary_path' => null, + + /* + * The sitemap generator uses a CrawlProfile implementation to determine + * which urls should be crawled for the sitemap. + */ + 'crawl_profile' => Profile::class, + +]; diff --git a/resources/js/Pages/Client/Additional-educations/Show.vue b/resources/js/Pages/Client/Additional-educations/Show.vue index fcf55c5..8ddcd74 100644 --- a/resources/js/Pages/Client/Additional-educations/Show.vue +++ b/resources/js/Pages/Client/Additional-educations/Show.vue @@ -17,11 +17,13 @@ import ProgramTitle from "@/Components/BuilderUi/AdditionalEducationPrograms/Pro import ProgramBuilder from "@/Components/BuilderUi/AdditionalEducationPrograms/ProgramBuilder.vue"; import AdditionalEducationProgramItemBreadcrumbs from "@/Components/BuilderUi/AdditionalEducationPrograms/AdditionalEducationProgramItemBreadcrumbs.vue"; +import AppHead from "@/Components/AppHead.vue"; export default { name: "Show", components: { + AppHead, AdditionalEducationProgramItemBreadcrumbs, ProgramBuilder, ProgramTitle, @@ -34,7 +36,7 @@ export default { AdminIndexHeader, ClientPost, ClientPostFilter, - ClientFooterDown, ClientScrollTimeline, Link, FsLightbox, Head}, + ClientFooterDown, ClientScrollTimeline, Link, Head}, data() { return { @@ -45,7 +47,10 @@ export default { type: Object, }, breadcrumbs: { - type: Object + type: Object, + }, + seo: { + type: Object, } }, methods: { @@ -65,10 +70,10 @@ export default {