This commit is contained in:
f4ilji
2024-10-28 12:57:16 +05:00
parent e4678fc0d2
commit 6d516d1750
233 changed files with 6312 additions and 1314 deletions
@@ -8,6 +8,7 @@ use App\Helpers\ByteConverter;
use App\Models\Category;
use App\Models\CustomForm;
use App\Models\Page;
use App\Models\PageReferenceList;
use App\Models\Post;
use Filament\Forms;
use Filament\Forms\Components\Builder;
@@ -379,6 +380,13 @@ class ContentBuilderItem
->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)
+38 -2
View File
@@ -9,10 +9,13 @@ use App\Helpers\ByteConverter;
use App\Models\Category;
use App\Models\CustomForm;
use App\Models\Page;
use App\Models\PageReferenceList;
use App\Models\Post;
use Filament\Forms;
use Filament\Forms\Components\Builder;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\Section;
@@ -23,6 +26,8 @@ use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Form;
use Filament\Resources\Components\Tab;
use Filament\Resources\Pages\CreateRecord;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
@@ -79,6 +84,30 @@ class PostForm
SpatieTagsInput::make('tags')->label('Тэги'),
Forms\Components\TagsInput::make('authors')
->label('Авторы')->placeholder('Добавить автора'),
Section::make('Отложенная публикация')->schema([
Grid::make(2)->schema([
Toggle::make('publish_setting.publish_after')
->label('Включить')
->inline(false)
->default(false)
->live(),
DateTimePicker::make('publish_setting.publish_at')
->label('Дата публикации')
->native()
->displayFormat('d/m/Y')
->required(fn (Forms\Get $get) => $get('publish_setting.publish_after'))
->disabled(fn (Forms\Get $get) => !$get('publish_setting.publish_after'))
->minDate(Carbon::now()->subWeek())
->maxDate(Carbon::now()->addMonth()),
]),
]),
Section::make('Публикация в сервисах')->schema([
Forms\Components\Grid::make()->schema([
Toggle::make('publication.vk')->label('Публикация в VK')->default(true),
Toggle::make('publication.telegram')->label('Публикация в Telegram')->default(true),
]),
]),
]),
Tabs\Tab::make('Содержание новости')
->schema([
@@ -89,7 +118,7 @@ class PostForm
TextInput::make('content')
->label('')
->live(onBlur: true)
->afterStateUpdated(function (string $operation, string $state, Forms\Set $set, $get) {
->afterStateUpdated(function (string $operation, string|null $state, Forms\Set $set, $get) {
}),
]),
Builder\Block::make('paragraph')->label('Текст')
@@ -440,8 +469,16 @@ class PostForm
->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()
->required()
->blockNumbers(false)
->collapsible()
->blockPickerColumns(3)
@@ -468,7 +505,6 @@ class PostForm
->directory('images'),
]),
]),
])
]);
}
@@ -125,8 +125,6 @@ class MainSliderResource extends Resource
Toggle::make('is_active')->default(true)->label('Активный слайдер')->inline(false),
]),
]);
}
@@ -0,0 +1,177 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\PageReferenceListResource\Pages;
use App\Filament\Resources\PageReferenceListResource\RelationManagers;
use App\Models\Event;
use App\Models\Page;
use App\Models\PageReferenceList;
use App\Models\Post;
use Filament\Forms;
use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Repeater;
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\Str;
class PageReferenceListResource extends Resource
{
protected static ?string $model = PageReferenceList::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Section::make('')->schema([
Tabs::make('Tabs')
->tabs([
Tabs\Tab::make('Основная информация')
->schema([
Forms\Components\Grid::make(2)->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([
Repeater::make('content')->schema([
Forms\Components\Section::make('Быстрая настройка ресурса')->schema([
Forms\Components\Grid::make()->schema([
Forms\Components\Select::make('model_select')
->name('')
->label('Выбор типа данных')
->options([
'Post' => 'Новость',
'Page' => 'Страница',
'Event' => 'Мероприятие',
'Custom' => 'Кастомная ссылка',
])
->afterStateUpdated(function (string $operation, string|null $state, Forms\Set $set, Forms\Get $get) {
if ($get('model_select') === 'Custom') {
$set('model', null);
$set('title', null);
$set('content', null);
$set('link', null);
};
})->live(onBlur: true),
Forms\Components\Select::make('model')
->label('Поиск данных')
->name('')
->live(onBlur: true)
->searchable()
->live(onBlur: true)
->afterStateUpdated(function (string $operation, string|null $state, Forms\Set $set, Forms\Get $get) {
if ($get('model_select') === 'Post') {
$post = Post::find($state);
$set('title', $post->title);
$relativeUrl = parse_url(route('client.post.show', $post->slug), PHP_URL_PATH);
$set('link', $relativeUrl);
};
if ($get('model_select') === 'Page') {
$page = Page::find($state);
$set('title', $page->title);
$set('link', $page->path);
};
if ($get('model_select') === 'Event') {
$event = Event::find($state);
$set('title', $event->title);
$relativeUrl = parse_url(route('client.event.show', $event->slug), PHP_URL_PATH);
$set('link', $relativeUrl);
};
})
->options(function (Forms\Get $get) {
if ($get('model_select') === 'Post') {
return Post::where('status', '=', 'published')->pluck('title', 'id');
};
if ($get('model_select') === 'Page') {
return Page::where('title', '!=', null)->pluck('title', 'id');
};
if ($get('model_select') === 'Event') {
return Event::all()->pluck('title', 'id');
};
if ($get('model_select') === 'Custom') {
return [];
};
}),
]),
]),
TextInput::make('title')->label('Title')->required(),
FileUpload::make('image')
->label('Изображение')
->image()
->optimize('webp')
->resize(50)
->disk('public')
->directory('images')
->imageEditor(),
Forms\Components\Grid::make(2)->schema([
Forms\Components\TextInput::make('link')
->label('Ссылка ресурса')
->required(),
Forms\Components\TextInput::make('link_text')
->default('Читать')
->label('Текст кнопки')
->required(),
]),
])->collapsed()->required(),
]),
]),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
//
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListPageReferenceLists::route('/'),
'create' => Pages\CreatePageReferenceList::route('/create'),
'edit' => Pages\EditPageReferenceList::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,12 @@
<?php
namespace App\Filament\Resources\PageReferenceListResource\Pages;
use App\Filament\Resources\PageReferenceListResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreatePageReferenceList extends CreateRecord
{
protected static string $resource = PageReferenceListResource::class;
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\PageReferenceListResource\Pages;
use App\Filament\Resources\PageReferenceListResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditPageReferenceList extends EditRecord
{
protected static string $resource = PageReferenceListResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\PageReferenceListResource\Pages;
use App\Filament\Resources\PageReferenceListResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListPageReferenceLists extends ListRecords
{
protected static string $resource = PageReferenceListResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}
+2 -1
View File
@@ -103,7 +103,8 @@ class PostResource extends Resource implements HasShieldPermissions
'update',
'delete',
'delete_any',
'publish'
'publish',
'view_only_own_records'
];
}
@@ -4,8 +4,10 @@ namespace App\Filament\Resources\PostResource\Pages;
use App\Enums\PostStatus;
use App\Filament\Resources\PostResource;
use App\Jobs\CreateVkPost;
use App\Models\Post;
use App\Models\User;
use App\Services\VK\VkService;
use Carbon\Carbon;
use Closure;
use Filament\Actions;
@@ -14,6 +16,7 @@ use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Messages\BroadcastMessage;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use PhpParser\Node\Expr\AssignOp\Mod;
use VK\Client\VKApiClient;
@@ -28,12 +31,17 @@ class CreatePost extends CreateRecord
protected static string $resource = PostResource::class;
protected array $seoData;
protected array $publicationAgreements;
protected function mutateFormDataBeforeCreate(array $data): array
{
$this->publicationAgreements = $data['publication'];
unset($data['publication']);
$this->seoData = $this->generateSeo($data);
$data['preview_text'] = $this->setPreviewText($data);
$data['publish_at'] = $this->setPublishDateTime($data['status']);
$data['publish_at'] = $this->setPublishDateTime($data['publish_setting']);
unset($data['publish_setting']);
$data['search_data'] = $this->generateSearchData($data['content']);
$data['reading_time'] = $this->calculateReadingTime($data['search_data']);
return $data;
@@ -43,6 +51,7 @@ class CreatePost extends CreateRecord
{
$this->record->seo()->create($this->seoData);
$this->sendNotify($this->record, auth()->user());
$this->postToSocialMedia($this->publicationAgreements, $this->record->content, $this->record->title, Carbon::parse($this->record->publish_at)->timestamp);
}
private function generateSeo(array $data) : array
@@ -62,7 +71,6 @@ class CreatePost extends CreateRecord
];
}
private function setPreviewText(array $data) : string
{
$rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']);
@@ -126,10 +134,10 @@ class CreatePost extends CreateRecord
])->sendToDatabase($recipient);
}
private function setPublishDateTime(PostStatus $status) : Carbon|null
private function setPublishDateTime(array $data) : Carbon|null
{
if ($status !== PostStatus::PUBLISHED) {
return null;
if ($data['publish_after'] === true) {
return Carbon::parse($data['publish_at']);
}
return Carbon::now();
}
@@ -243,6 +251,58 @@ class CreatePost extends CreateRecord
return $data;
}
private function postToSocialMedia($settings, $content, $title, $publish_date) : void
{
if ($this->record->status === PostStatus::PUBLISHED) {
if ($settings['vk']) {
$text = "";
foreach ($content as $block) {
$text .= $this->generateContentToVK($block);
}
$images = $this->generateImageLinksToVK($this->record->images);
$post_id = $this->record->id;
dispatch(new CreateVkPost($title, $text, $images, $post_id, $publish_date));
}
}
}
private function generateContentToVK($block) : string
{
$data = "";
switch ($block['type']) {
case 'paragraph':
// Удаляем все HTML-теги и заменяем закрывающие теги p и h2 на двойной отступ
$content = preg_replace('/<\/(p|h2)>/', "\n\n", $block['data']['content']);
$data .= strip_tags($content);
break;
case 'heading':
// Удаляем теги заголовка и добавляем двойной отступ
$data .= $block['data']['content'] . "\n\n";
break;
}
return $data;
}
private function generateImageLinksToVK($images)
{
$imageUrls = array_map(function ($file) {
return url(Storage::url($file)); // Добавляем домен
}, $images);
return $imageUrls; // Возвращаем массив с полными URL изображений
}
}
@@ -4,9 +4,11 @@ namespace App\Filament\Resources\PostResource\Pages;
use App\Enums\PostStatus;
use App\Filament\Resources\PostResource;
use App\Jobs\UpdateVkPost;
use Carbon\Carbon;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class EditPost extends EditRecord
@@ -14,11 +16,14 @@ class EditPost extends EditRecord
protected static string $resource = PostResource::class;
protected array $seoData;
protected array $publicationAgreements;
protected function mutateFormDataBeforeSave(array $data): array
{
$this->seoData = $this->generateSeo($data);
$this->publicationAgreements = $data['publication'];
unset($data['publication']);
$data['preview_text'] = $this->setPreviewText($data);
$data['publish_at'] = $this->setPublishDateTime($data['status'], $this->record->publish_at);
$data['search_data'] = $this->generateSearchData($data['content']);
@@ -30,6 +35,7 @@ class EditPost extends EditRecord
protected function afterSave(): void
{
$this->record->seo()->update($this->seoData);
$this->postToSocialMedia($this->publicationAgreements, $this->record->content, $this->record->title, Carbon::parse($this->record->publish_at)->timestamp);
}
private function setPreviewText(array $data) : string
@@ -59,8 +65,6 @@ class EditPost extends EditRecord
return $block;
}
private function generateSeo(array $data) : array
{
$title = $data['title'];
@@ -166,6 +170,59 @@ class EditPost extends EditRecord
return $data;
}
private function generateContentToVK($block) : string
{
$data = "";
switch ($block['type']) {
case 'paragraph':
// Удаляем все HTML-теги и заменяем закрывающие теги p и h2 на двойной отступ
$content = preg_replace('/<\/(p|h2)>/', "\n\n", $block['data']['content']);
$data .= strip_tags($content);
break;
case 'heading':
// Удаляем теги заголовка и добавляем двойной отступ
$data .= $block['data']['content'] . "\n\n";
break;
}
return $data;
}
private function postToSocialMedia($settings, $content, $title, $publish_date) : void
{
if ($this->record->status === PostStatus::PUBLISHED) {
if ($settings['vk']) {
$text = "";
foreach ($content as $block) {
$text .= $this->generateContentToVK($block);
}
$images = $this->generateImageLinksToVK($this->record->images);
$post_id = $this->record->id;
dispatch(new UpdateVkPost($title, $text, $images, $post_id, $publish_date));
}
}
}
private function generateImageLinksToVK($images)
{
$imageUrls = array_map(function ($file) {
return url(Storage::url($file)); // Добавляем домен
}, $images);
return $imageUrls; // Возвращаем массив с полными URL изображений
}
@@ -9,21 +9,26 @@ use Filament\Actions;
use Filament\Resources\Components\Tab;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
class ListPosts extends ListRecords
{
public \Illuminate\Support\Collection $postsByStatuses;
protected static string $resource = PostResource::class;
public function __construct()
public function getPostsByStatuses(): \Illuminate\Support\Collection
{
$this->postsByStatuses = Post::select('status', DB::raw('count(*) as post_count'))
return Post::select('status', DB::raw('count(*) as post_count'))
->groupBy('status')
->when($this->canViewOnlyOwnRecords(), function (Builder $query) {
$query->where('user_id', auth()->id());
})
->pluck('post_count', 'status');
}
protected static string $resource = PostResource::class;
protected function canViewOnlyOwnRecords(): bool
{
return auth()->user()->can('view_only_own_records_post');
}
protected function getHeaderActions(): array
{
@@ -34,11 +39,21 @@ class ListPosts extends ListRecords
public function getTabs(): array
{
$postsByStatuses = $this->getPostsByStatuses();
return [
'status' => Tab::make('Новости на рассмотрении')->modifyQueryUsing(function (Builder $query) {
$query->where('status', '=', PostStatus::VERIFICATION->value);
})->badge($this->postsByStatuses[PostStatus::VERIFICATION->value] ?? 0),
'All' => Tab::make('Все новости'),
if ($this->canViewOnlyOwnRecords()) {
$query->where('user_id', auth()->id());
}
})->badge($postsByStatuses[PostStatus::VERIFICATION->value] ?? 0),
'All' => Tab::make('Все новости')->modifyQueryUsing(function (Builder $query) {
if ($this->canViewOnlyOwnRecords()) {
$query->where('user_id', auth()->id());
}
}),
];
}
}
@@ -0,0 +1,402 @@
<?php
namespace App\Filament\Resources\Shield;
use BezhanSalleh\FilamentShield\Contracts\HasShieldPermissions;
use BezhanSalleh\FilamentShield\Facades\FilamentShield;
use BezhanSalleh\FilamentShield\FilamentShieldPlugin;
use BezhanSalleh\FilamentShield\Forms\ShieldSelectAllToggle;
use App\Filament\Resources\Shield\RoleResource\Pages;
use BezhanSalleh\FilamentShield\Support\Utils;
use Filament\Forms;
use Filament\Forms\Components\Component;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
class RoleResource extends Resource implements HasShieldPermissions
{
protected static ?string $recordTitleAttribute = 'name';
public static function getPermissionPrefixes(): array
{
return [
'view',
'view_any',
'create',
'update',
'delete',
'delete_any',
];
}
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Grid::make()
->schema([
Forms\Components\Section::make()
->schema([
Forms\Components\TextInput::make('name')
->label(__('filament-shield::filament-shield.field.name'))
->unique(ignoreRecord: true)
->required()
->maxLength(255),
Forms\Components\TextInput::make('guard_name')
->label(__('filament-shield::filament-shield.field.guard_name'))
->default(Utils::getFilamentAuthGuard())
->nullable()
->maxLength(255),
ShieldSelectAllToggle::make('select_all')
->onIcon('heroicon-s-shield-check')
->offIcon('heroicon-s-shield-exclamation')
->label(__('filament-shield::filament-shield.field.select_all.name'))
->helperText(fn (): HtmlString => new HtmlString(__('filament-shield::filament-shield.field.select_all.message')))
->dehydrated(fn ($state): bool => $state),
])
->columns([
'sm' => 2,
'lg' => 3,
]),
]),
Forms\Components\Tabs::make('Permissions')
->contained()
->tabs([
static::getTabFormComponentForResources(),
static::getTabFormComponentForPage(),
static::getTabFormComponentForWidget(),
static::getTabFormComponentForCustomPermissions(),
])
->columnSpan('full'),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->badge()
->label(__('filament-shield::filament-shield.column.name'))
->formatStateUsing(fn ($state): string => Str::headline($state))
->colors(['primary'])
->searchable(),
Tables\Columns\TextColumn::make('guard_name')
->badge()
->label(__('filament-shield::filament-shield.column.guard_name')),
Tables\Columns\TextColumn::make('permissions_count')
->badge()
->label(__('filament-shield::filament-shield.column.permissions'))
->counts('permissions')
->colors(['success']),
Tables\Columns\TextColumn::make('updated_at')
->label(__('filament-shield::filament-shield.column.updated_at'))
->dateTime(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListRoles::route('/'),
'create' => Pages\CreateRole::route('/create'),
'view' => Pages\ViewRole::route('/{record}'),
'edit' => Pages\EditRole::route('/{record}/edit'),
];
}
public static function getCluster(): ?string
{
return Utils::getResourceCluster() ?? static::$cluster;
}
public static function getModel(): string
{
return Utils::getRoleModel();
}
public static function getModelLabel(): string
{
return __('filament-shield::filament-shield.resource.label.role');
}
public static function getPluralModelLabel(): string
{
return __('filament-shield::filament-shield.resource.label.roles');
}
public static function shouldRegisterNavigation(): bool
{
return Utils::isResourceNavigationRegistered();
}
public static function getNavigationGroup(): ?string
{
return Utils::isResourceNavigationGroupEnabled()
? __('filament-shield::filament-shield.nav.group')
: '';
}
public static function getNavigationLabel(): string
{
return __('filament-shield::filament-shield.nav.role.label');
}
public static function getNavigationIcon(): string
{
return __('filament-shield::filament-shield.nav.role.icon');
}
public static function getNavigationSort(): ?int
{
return Utils::getResourceNavigationSort();
}
public static function getSlug(): string
{
return Utils::getResourceSlug();
}
public static function getNavigationBadge(): ?string
{
return Utils::isResourceNavigationBadgeEnabled()
? strval(static::getEloquentQuery()->count())
: null;
}
public static function isScopedToTenant(): bool
{
return Utils::isScopedToTenant();
}
public static function canGloballySearch(): bool
{
return Utils::isResourceGloballySearchable() && count(static::getGloballySearchableAttributes()) && static::canViewAny();
}
public static function getResourceEntitiesSchema(): ?array
{
return collect(FilamentShield::getResources())
->sortKeys()
->map(function ($entity) {
$sectionLabel = strval(
static::shield()->hasLocalizedPermissionLabels()
? FilamentShield::getLocalizedResourceLabel($entity['fqcn'])
: $entity['model']
);
return Forms\Components\Section::make($sectionLabel)
->description(fn () => new HtmlString('<span style="word-break: break-word;">' . Utils::showModelPath($entity['fqcn']) . '</span>'))
->compact()
->schema([
static::getCheckBoxListComponentForResource($entity),
])
->columnSpan(static::shield()->getSectionColumnSpan())
->collapsible();
})
->toArray();
}
public static function getResourceTabBadgeCount(): ?int
{
return collect(FilamentShield::getResources())
->map(fn ($resource) => count(static::getResourcePermissionOptions($resource)))
->sum();
}
public static function getResourcePermissionOptions(array $entity): array
{
return collect(Utils::getResourcePermissionPrefixes($entity['fqcn']))
->flatMap(function ($permission) use ($entity) {
$name = $permission . '_' . $entity['resource'];
$label = static::shield()->hasLocalizedPermissionLabels()
? FilamentShield::getLocalizedResourcePermissionLabel($permission)
: $name;
return [
$name => $label,
];
})
->toArray();
}
public static function setPermissionStateForRecordPermissions(Component $component, string $operation, array $permissions, ?Model $record): void
{
if (in_array($operation, ['edit', 'view'])) {
if (blank($record)) {
return;
}
if ($component->isVisible() && count($permissions) > 0) {
$component->state(
collect($permissions)
/** @phpstan-ignore-next-line */
->filter(fn ($value, $key) => $record->checkPermissionTo($key))
->keys()
->toArray()
);
}
}
}
public static function getPageOptions(): array
{
return collect(FilamentShield::getPages())
->flatMap(fn ($page) => [
$page['permission'] => static::shield()->hasLocalizedPermissionLabels()
? FilamentShield::getLocalizedPageLabel($page['class'])
: $page['permission'],
])
->toArray();
}
public static function getWidgetOptions(): array
{
return collect(FilamentShield::getWidgets())
->flatMap(fn ($widget) => [
$widget['permission'] => static::shield()->hasLocalizedPermissionLabels()
? FilamentShield::getLocalizedWidgetLabel($widget['class'])
: $widget['permission'],
])
->toArray();
}
public static function getCustomPermissionOptions(): ?array
{
return FilamentShield::getCustomPermissions()
->mapWithKeys(fn ($customPermission) => [
$customPermission => static::shield()->hasLocalizedPermissionLabels() ? str($customPermission)->headline()->toString() : $customPermission,
])
->toArray();
}
public static function getTabFormComponentForResources(): Component
{
return static::shield()->hasSimpleResourcePermissionView()
? static::getTabFormComponentForSimpleResourcePermissionsView()
: Forms\Components\Tabs\Tab::make('resources')
->label(__('filament-shield::filament-shield.resources'))
->visible(fn (): bool => (bool) Utils::isResourceEntityEnabled())
->badge(static::getResourceTabBadgeCount())
->schema([
Forms\Components\Grid::make()
->schema(static::getResourceEntitiesSchema())
->columns(static::shield()->getGridColumns()),
]);
}
public static function getCheckBoxListComponentForResource(array $entity): Component
{
$permissionsArray = static::getResourcePermissionOptions($entity);
return static::getCheckboxListFormComponent($entity['resource'], $permissionsArray, false);
}
public static function getTabFormComponentForPage(): Component
{
$options = static::getPageOptions();
$count = count($options);
return Forms\Components\Tabs\Tab::make('pages')
->label(__('filament-shield::filament-shield.pages'))
->visible(fn (): bool => (bool) Utils::isPageEntityEnabled() && $count > 0)
->badge($count)
->schema([
static::getCheckboxListFormComponent('pages_tab', $options),
]);
}
public static function getTabFormComponentForWidget(): Component
{
$options = static::getWidgetOptions();
$count = count($options);
return Forms\Components\Tabs\Tab::make('widgets')
->label(__('filament-shield::filament-shield.widgets'))
->visible(fn (): bool => (bool) Utils::isWidgetEntityEnabled() && $count > 0)
->badge($count)
->schema([
static::getCheckboxListFormComponent('widgets_tab', $options),
]);
}
public static function getTabFormComponentForCustomPermissions(): Component
{
$options = static::getCustomPermissionOptions();
$count = count($options);
return Forms\Components\Tabs\Tab::make('custom')
->label(__('filament-shield::filament-shield.custom'))
->visible(fn (): bool => (bool) Utils::isCustomPermissionEntityEnabled() && $count > 0)
->badge($count)
->schema([
static::getCheckboxListFormComponent('custom_permissions', $options),
]);
}
public static function getTabFormComponentForSimpleResourcePermissionsView(): Component
{
$options = FilamentShield::getAllResourcePermissions();
$count = count($options);
return Forms\Components\Tabs\Tab::make('resources')
->label(__('filament-shield::filament-shield.resources'))
->visible(fn (): bool => (bool) Utils::isResourceEntityEnabled() && $count > 0)
->badge($count)
->schema([
static::getCheckboxListFormComponent('resources_tab', $options),
]);
}
public static function getCheckboxListFormComponent(string $name, array $options, bool $searchable = true): Component
{
return Forms\Components\CheckboxList::make($name)
->label('')
->options(fn (): array => $options)
->searchable($searchable)
->afterStateHydrated(
fn (Component $component, string $operation, ?Model $record) => static::setPermissionStateForRecordPermissions(
component: $component,
operation: $operation,
permissions: $options,
record: $record
)
)
->dehydrated(fn ($state) => ! blank($state))
->bulkToggleable()
->gridDirection('row')
->columns(static::shield()->getCheckboxListColumns())
->columnSpan(static::shield()->getCheckboxListColumnSpan());
}
public static function shield(): FilamentShieldPlugin
{
return FilamentShieldPlugin::get();
}
}
@@ -0,0 +1,43 @@
<?php
namespace App\Filament\Resources\Shield\RoleResource\Pages;
use App\Filament\Resources\Shield\RoleResource;
use BezhanSalleh\FilamentShield\Support\Utils;
use Filament\Resources\Pages\CreateRecord;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
class CreateRole extends CreateRecord
{
protected static string $resource = RoleResource::class;
public Collection $permissions;
protected function mutateFormDataBeforeCreate(array $data): array
{
$this->permissions = collect($data)
->filter(function ($permission, $key) {
return ! in_array($key, ['name', 'guard_name', 'select_all']);
})
->values()
->flatten()
->unique();
return Arr::only($data, ['name', 'guard_name']);
}
protected function afterCreate(): void
{
$permissionModels = collect();
$this->permissions->each(function ($permission) use ($permissionModels) {
$permissionModels->push(Utils::getPermissionModel()::firstOrCreate([
/** @phpstan-ignore-next-line */
'name' => $permission,
'guard_name' => $this->data['guard_name'],
]));
});
$this->record->syncPermissions($permissionModels);
}
}
@@ -0,0 +1,50 @@
<?php
namespace App\Filament\Resources\Shield\RoleResource\Pages;
use App\Filament\Resources\Shield\RoleResource;
use BezhanSalleh\FilamentShield\Support\Utils;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
class EditRole extends EditRecord
{
protected static string $resource = RoleResource::class;
public Collection $permissions;
protected function getActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
protected function mutateFormDataBeforeSave(array $data): array
{
$this->permissions = collect($data)
->filter(function ($permission, $key) {
return ! in_array($key, ['name', 'guard_name', 'select_all']);
})
->values()
->flatten()
->unique();
return Arr::only($data, ['name', 'guard_name']);
}
protected function afterSave(): void
{
$permissionModels = collect();
$this->permissions->each(function ($permission) use ($permissionModels) {
$permissionModels->push(Utils::getPermissionModel()::firstOrCreate([
'name' => $permission,
'guard_name' => $this->data['guard_name'],
]));
});
$this->record->syncPermissions($permissionModels);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Shield\RoleResource\Pages;
use App\Filament\Resources\Shield\RoleResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListRoles extends ListRecords
{
protected static string $resource = RoleResource::class;
protected function getActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Shield\RoleResource\Pages;
use App\Filament\Resources\Shield\RoleResource;
use Filament\Actions;
use Filament\Resources\Pages\ViewRecord;
class ViewRole extends ViewRecord
{
protected static string $resource = RoleResource::class;
protected function getActions(): array
{
return [
Actions\EditAction::make(),
];
}
}
+13
View File
@@ -37,11 +37,24 @@ class UserResource extends Resource implements HasShieldPermissions
->required()
->maxLength(255),
Forms\Components\DateTimePicker::make('email_verified_at'),
Forms\Components\TextInput::make('password')
->password()
->required(fn (string $context): bool => $context === 'create')
->dehydrated(fn ($state) => filled($state))
->maxLength(255),
Forms\Components\Select::make('roles')
->relationship('roles', 'name')
->multiple()
->preload()
->searchable(),
Forms\Components\Select::make('permissions')
->relationship('permissions', 'name')
->multiple()
->preload()
->searchable()
]);
}
@@ -6,10 +6,14 @@ use App\Enums\FormEducation;
use App\Http\Resources\AdditionalEducationCategoryPreviewResource;
use App\Http\Resources\AdditionalEducationCategoryResource;
use App\Http\Resources\AdditionalEducationResource;
use App\Http\Resources\ClientBreadcrumbPage;
use App\Http\Resources\ClientBreadcrumbSection;
use App\Http\Resources\ClientBreadcrumbSubSection;
use App\Http\Resources\DirectionAdditionalEducationResource;
use App\Models\AdditionalEducation;
use App\Models\AdditionalEducationCategory;
use App\Models\DirectionAdditionalEducation;
use App\Models\Page;
use Illuminate\Http\Request;
use Inertia\Inertia;
@@ -85,19 +89,51 @@ class ClientAdditionalEducationController extends Controller
'content' => $categoriesContent,
],
];
$routeUrl = route('client.additionalEducation.index');
$path = ltrim(parse_url($routeUrl, PHP_URL_PATH), '/');
$page = Page::where('path', '=', $path)->with('section.pages.section', 'section.mainSection')->first();
if (isset($page->section)) {
$breadcrumbs = [
'mainSection' => new ClientBreadcrumbSection($page->section->mainSection),
'subSection' => new ClientBreadcrumbSubSection($page->section),
'page' => new ClientBreadcrumbPage($page),
];
} else {
$breadcrumbs = null;
}
return Inertia::render('Client/Additional-educations/Index',
compact(
'directionAdditionalEducations',
'additionalEducations',
'filters',
'forms_education',
'categories'
'categories',
'breadcrumbs'
));
}
public function show(string $slug)
{
$additionalEducation = new AdditionalEducationResource(AdditionalEducation::query()->with('category.direction')->where('slug', $slug)->first());
return Inertia::render('Client/Additional-educations/Show', compact('additionalEducation'));
$routeUrl = route('client.additionalEducation.index');
$path = ltrim(parse_url($routeUrl, PHP_URL_PATH), '/');
$page = Page::where('path', '=', $path)->with('section.pages.section', 'section.mainSection')->first();
if (isset($page->section)) {
$breadcrumbs = [
'mainSection' => new ClientBreadcrumbSection($page->section->mainSection),
'subSection' => new ClientBreadcrumbSubSection($page->section),
'page' => new ClientBreadcrumbPage($page),
];
} else {
$breadcrumbs = null;
}
return Inertia::render('Client/Additional-educations/Show', compact('additionalEducation', 'breadcrumbs'));
}
}
+37 -2
View File
@@ -2,12 +2,16 @@
namespace App\Http\Controllers;
use App\Http\Resources\ClientBreadcrumbPage;
use App\Http\Resources\ClientBreadcrumbSection;
use App\Http\Resources\ClientBreadcrumbSubSection;
use App\Http\Resources\ClientEventCategoryResource;
use App\Http\Resources\ClientEventFullResource;
use App\Http\Resources\ClientEventResource;
use App\Http\Resources\ClientNavigationResource;
use App\Models\Event;
use App\Models\EventCategory;
use App\Models\Page;
use Carbon\Carbon;
use DateTime;
use Illuminate\Http\Request;
@@ -24,13 +28,44 @@ class ClientEventController extends Controller
$events = $this->getEvents($currentDate);
$categories = ClientEventCategoryResource::collection(EventCategory::has('events')->get());
return Inertia::render('Client/Events/Index', compact('eventDates', 'events', 'currentDate', 'filters', 'categories'));
$routeUrl = route('client.event.index');
$path = ltrim(parse_url($routeUrl, PHP_URL_PATH), '/');
$page = Page::where('path', '=', $path)->with('section.pages.section', 'section.mainSection')->first();
if (isset($page->section)) {
$breadcrumbs = [
'mainSection' => new ClientBreadcrumbSection($page->section->mainSection),
'subSection' => new ClientBreadcrumbSubSection($page->section),
'page' => new ClientBreadcrumbPage($page),
];
} else {
$breadcrumbs = null;
}
return Inertia::render('Client/Events/Index', compact('eventDates', 'events', 'currentDate', 'filters', 'categories', 'breadcrumbs'));
}
public function show(string $slug)
{
$event = new ClientEventFullResource(Event::where('slug', '=', $slug)->with('category')->first());
return Inertia::render('Client/Events/Show', compact('event'));
$routeUrl = route('client.event.index');
$path = ltrim(parse_url($routeUrl, PHP_URL_PATH), '/');
$page = Page::where('path', '=', $path)->with('section.pages.section', 'section.mainSection')->first();
if (isset($page->section)) {
$breadcrumbs = [
'mainSection' => new ClientBreadcrumbSection($page->section->mainSection),
'subSection' => new ClientBreadcrumbSubSection($page->section),
'page' => new ClientBreadcrumbPage($page),
];
} else {
$breadcrumbs = null;
}
return Inertia::render('Client/Events/Show', compact('event', 'breadcrumbs'));
}
private function getCurrentDate(Request $request): array
+40 -3
View File
@@ -3,15 +3,21 @@
namespace App\Http\Controllers;
use App\Http\Resources\CategoryResource;
use App\Http\Resources\ClientBreadcrumbPage;
use App\Http\Resources\ClientBreadcrumbSection;
use App\Http\Resources\ClientBreadcrumbSubSection;
use App\Http\Resources\ClientNavigationResource;
use App\Http\Resources\ClientPostListResource;
use App\Http\Resources\ClientTagResource;
use App\Http\Resources\MainSectionResource;
use App\Http\Resources\PageResource;
use App\Http\Resources\PostResource;
use App\Models\Category;
use App\Models\MainSection;
use App\Models\Page;
use App\Models\Post;
use App\Models\Tag;
use Carbon\Carbon;
use Doctrine\DBAL\Schema\Column;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@@ -33,6 +39,7 @@ class ClientPostController extends Controller
->with('category')
->select('title', 'slug', 'authors', 'category_id', 'preview', 'search_data', 'created_at')
->where('status', '=', 'published')
->where('publish_at', '<', Carbon::now())
->when(request()->input('search'), function ($query, $search) {
$query->whereRaw('LOWER(title) like ?', ["%".strtolower($search)."%"]);
})
@@ -100,13 +107,43 @@ class ClientPostController extends Controller
],
];
return Inertia::render('Client/Posts/Index', compact('filters', 'posts', 'categories', 'tags'));
$routeUrl = route('client.post.index');
$path = ltrim(parse_url($routeUrl, PHP_URL_PATH), '/');
$page = Page::where('path', '=', $path)->with('section.pages.section', 'section.mainSection')->first();
if (isset($page->section)) {
$breadcrumbs = [
'mainSection' => new ClientBreadcrumbSection($page->section->mainSection),
'subSection' => new ClientBreadcrumbSubSection($page->section),
'page' => new ClientBreadcrumbPage($page),
];
} else {
$breadcrumbs = null;
}
return Inertia::render('Client/Posts/Index', compact('filters', 'posts', 'categories', 'tags', 'breadcrumbs'));
}
public function show(Request $request, $slug)
{
$post = new PostResource(Post::where('slug', $slug)->firstOrFail());
return Inertia::render('Client/Posts/Show', compact('post'));
$post = new PostResource(Post::where('slug', $slug)->where('publish_at', '<', Carbon::now())->firstOrFail());
$routeUrl = route('client.post.index');
$path = ltrim(parse_url($routeUrl, PHP_URL_PATH), '/');
$page = Page::where('path', '=', $path)->with('section.pages.section', 'section.mainSection')->first();
if (isset($page->section)) {
$breadcrumbs = [
'mainSection' => new ClientBreadcrumbSection($page->section->mainSection),
'subSection' => new ClientBreadcrumbSubSection($page->section),
'page' => new ClientBreadcrumbPage($page),
];
} else {
$subSectionPages = null;
$breadcrumbs = null;
}
return Inertia::render('Client/Posts/Show', compact('post', 'breadcrumbs'));
}
@@ -0,0 +1,15 @@
<?php
namespace App\Http\Controllers;
use App\Http\Resources\ClientPageReferenceListResource;
use App\Models\PageReferenceList;
use Illuminate\Http\Request;
class ClientWidgetPageReferenceListController extends Controller
{
public function show(string $slug)
{
return new ClientPageReferenceListResource(PageReferenceList::query()->where('slug', $slug)->first());
}
}
+3
View File
@@ -22,6 +22,8 @@ use App\Models\MainSection;
use App\Models\MainSlider;
use App\Models\Post;
use App\Services\Filament\Icon\ArrayToCollectionService;
use App\Services\Vicon\EducationalProgram\EducationalProgramService;
use Carbon\Carbon;
use DateTime;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
@@ -63,6 +65,7 @@ class MainController extends Controller
$posts = PostThumbnailResource::collection(Post::query()
->select('title', 'slug', 'authors', 'preview_text', 'category_id', 'preview', 'search_data', 'created_at')
->with('category')
->where('publish_at', '<', Carbon::now())
->where('status', '=', PostStatus::PUBLISHED)
->orderBy('publish_at', 'desc')->limit(3)
->get());
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers;
use Carbon\Carbon;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Inertia\Inertia;
class VkAuthController extends Controller
{
}
+34 -16
View File
@@ -2,8 +2,16 @@
namespace App\Http\Controllers;
use App\Jobs\CreateVkPost;
use App\Services\VK\VkAuthService;
use App\Services\VK\VkService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Inertia\Inertia;
use Symfony\Component\Finder\Finder;
use VK\Client\VKApiClient;
use VK\OAuth\Scopes\VKOAuthUserScope;
use VK\OAuth\VKOAuth;
@@ -16,33 +24,43 @@ class VkPostController extends Controller
private VkService $vkService;
public function __construct()
{
$this->vkService = new VkService(new VKApiClient());
$this->wall_token = env('WALL_ACCESS_VK_TOKEN');
}
public function index()
{
$oauth = new VKOAuth();
$client_id = 52468445;
$redirect_uri = 'https://crawdad-fresh-bream.ngrok-free.app/';
$display = VKOAuthDisplay::PAGE;
$scope = array(VKOAuthUserScope::WALL, VKOAuthUserScope::PHOTOS);
$state = 'dJZ3N05uZc9jpcEgxD6y';
$groups_ids = array(227826614);
$browser_url = $oauth->getAuthorizeUrl(VKOAuthResponseType::TOKEN, $client_id, $redirect_uri, $display, $scope, $state, $groups_ids);
return redirect($browser_url);
}
public function wall()
{
return $this->vkService->createAlbum('Тестовый альбом');
dd($this->vkService->getPostById(39));
}
public function getImages()
{
// Укажите путь к директории
$directory = 'public/images';
// Получаем все файлы из директории
$files = Storage::files($directory);
// Фильтруем только изображения (например, jpg, png)
$images = array_filter($files, function ($file) {
return in_array(pathinfo($file, PATHINFO_EXTENSION), ['webp', 'jpeg', 'png', 'gif']);
});
// Формируем полный URL для каждого изображения
$imageUrls = array_map(function ($file) {
return url(Storage::url($file)); // Добавляем домен
}, $images);
return $imageUrls; // Возвращаем массив с полными URL изображений
}
}
+1
View File
@@ -21,6 +21,7 @@ class Kernel extends HttpKernel
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ClientPageReferenceListResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ClientPageReferenceResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace App\Jobs;
use App\Enums\LevelEducational;
use App\Models\DirectionStudy;
use App\Services\Vicon\DirectionStudy\DirectionStudyService;
use App\Services\VK\VkService;
use Carbon\Carbon;
use Filament\Notifications\Notification;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use VK\Client\VKApiClient;
class CreateVkPost implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct(
readonly private string $title,
readonly private string $text,
readonly private array $images = [],
readonly private int $post_id,
readonly private int $publish_date,
)
{}
public function handle()
{
try {
$vkService = new VkService();
$vk_post = $vkService->createPost($this->title, $this->text, $this->images, $this->publish_date);
DB::table('posts_vk_posts')->insert(
[
'post_id' => $this->post_id,
'vk_post_id' => $vk_post['post_id'],
'unchange_time_after' => Carbon::now()->addWeek(),
]
);
} catch (\Exception $e) {
Log::error('Ошибка при создании поста: ' . $e->getMessage());
throw $e; // Перебрасываем исключение для повторной попытки
}
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace App\Jobs;
use App\Enums\LevelEducational;
use App\Models\DirectionStudy;
use App\Services\Vicon\DirectionStudy\DirectionStudyService;
use App\Services\VK\VkService;
use Carbon\Carbon;
use Filament\Notifications\Notification;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use VK\Client\VKApiClient;
class UpdateVkPost implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct(
readonly private string $title,
readonly private string $text,
readonly private array $images = [],
readonly private int $post_id,
readonly private int $publish_date,
)
{}
public function handle()
{
try {
$postRelation = DB::table('posts_vk_posts')->select()->where('post_id', $this->post_id)->first();
$vkService = new VkService();
$vkService->updatePost($postRelation->vk_post_id, $this->title, $this->text, $this->images, $this->publish_date);
} catch (\Exception $e) {
Log::error('Ошибка при создании поста: ' . $e->getMessage());
throw $e; // Перебрасываем исключение для повторной попытки
}
}
}
+3
View File
@@ -76,6 +76,9 @@ class AcceptInvitation extends SimplePage
'email' => $this->invitationModel->email,
]);
$user->assignRole(config('filament-shield.invited_user.name', 'invited_user'));
AcceptedInvitation::create([
'sender_id' => $this->invitationModel->user_id,
'receiver_id' => $user->id,
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PageReferenceList extends Model
{
use HasFactory;
protected $guarded = false;
protected $casts = [
'content' => 'array',
];
}
+1 -2
View File
@@ -15,13 +15,12 @@ class Post extends Model
protected $guarded = false;
public function category() : BelongsTo
{
return $this->belongsTo(Category::class);
}
public function seo()
{
return $this->morphOne(Seo::class, 'seoable');
+3 -1
View File
@@ -106,7 +106,9 @@ class User extends Authenticatable implements FilamentUser
case "admin":
return $this->hasRole(Utils::getSuperAdminName());
case "dashboard":
return $this->hasRole(config('filament-shield.dashboard_user.name', 'dashboard_user')) || $this->hasRole(Utils::getSuperAdminName());
return $this->hasRole(config('filament-shield.dashboard_user.name', 'dashboard_user'))
|| $this->hasRole(Utils::getSuperAdminName())
|| $this->hasRole(config('filament-shield.invited_user.name', 'invited_user'));
default:
return false;
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\PageReferenceList;
use Illuminate\Auth\Access\HandlesAuthorization;
class PageReferenceListPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_page::reference::list');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, PageReferenceList $pageReferenceList): bool
{
return $user->can('view_page::reference::list');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_page::reference::list');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, PageReferenceList $pageReferenceList): bool
{
return $user->can('update_page::reference::list');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, PageReferenceList $pageReferenceList): bool
{
return $user->can('delete_page::reference::list');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_page::reference::list');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, PageReferenceList $pageReferenceList): bool
{
return $user->can('force_delete_page::reference::list');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_page::reference::list');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, PageReferenceList $pageReferenceList): bool
{
return $user->can('restore_page::reference::list');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_page::reference::list');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, PageReferenceList $pageReferenceList): bool
{
return $user->can('replicate_page::reference::list');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_page::reference::list');
}
}
+6 -6
View File
@@ -15,7 +15,7 @@ class RolePolicy
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_role');
return $user->can('view_any_shield::role');
}
/**
@@ -23,7 +23,7 @@ class RolePolicy
*/
public function view(User $user, Role $role): bool
{
return $user->can('view_role');
return $user->can('view_shield::role');
}
/**
@@ -31,7 +31,7 @@ class RolePolicy
*/
public function create(User $user): bool
{
return $user->can('create_role');
return $user->can('create_shield::role');
}
/**
@@ -39,7 +39,7 @@ class RolePolicy
*/
public function update(User $user, Role $role): bool
{
return $user->can('update_role');
return $user->can('update_shield::role');
}
/**
@@ -47,7 +47,7 @@ class RolePolicy
*/
public function delete(User $user, Role $role): bool
{
return $user->can('delete_role');
return $user->can('delete_shield::role');
}
/**
@@ -55,7 +55,7 @@ class RolePolicy
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_role');
return $user->can('delete_any_shield::role');
}
/**
+128 -8
View File
@@ -2,36 +2,140 @@
namespace App\Services\VK\Album;
use App\Services\VK\VkAuthService;
use CURLFile;
use GuzzleHttp\Client;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Log;
use VK\Client\VKApiClient;
class VkAlbumService
{
private VKApiClient $vk;
private VkAuthService $vkAuthService;
private string $wallToken;
private string $serviceToken;
private string $publicId;
private string $publicDomain;
public function __construct(VKApiClient $vk) {
$this->vk = $vk;
$this->wallToken = env('WALL_ACCESS_VK_TOKEN');
$this->vkAuthService = new VkAuthService();
$this->serviceToken = env('SERVICE_ACCESS_VK_KEY');
$this->publicId = env('PUBLIC_ID');
$this->publicDomain = env('PUBLIC_DOMAIN');
}
public function getServerForUploadImages()
public function getServerForUploadImages($album_id, $group_id)
{
return $this->vk->photos()->getUploadServer($this->wallToken, array(
''
));
return $this->vk->photos()->getUploadServer(
$this->vkAuthService->getToken()->access_token,
array(
'album_id' => $album_id,
'group_id' => $group_id,
)
);
}
public function uploadImagesToUploadServer($uploadUrl, $images)
{
// Массив для хранения локальных путей к загруженным изображениям
$localFiles = [];
// Загрузка изображений из URL
foreach ($images as $imageUrl) {
$localFile = tempnam(sys_get_temp_dir(), 'img_') . '.webp';
file_put_contents($localFile, file_get_contents($imageUrl));
$localFiles[] = $localFile;
}
// Подготовка данных для отправки
$postFields = [];
foreach ($localFiles as $index => $localFile) {
// Добавляем файл в массив
$postFields['file' . ($index + 1)] = new CURLFile($localFile);
}
// Инициализация cURL
$ch = curl_init();
// Установка параметров cURL
curl_setopt($ch, CURLOPT_URL, $uploadUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
// Выполнение запроса
$response = curl_exec($ch);
curl_close($ch);
// Проверка на ошибки
if (curl_errno($ch)) {
return 'Ошибка cURL: ' . curl_error($ch);
}
// Закрытие cURL
// Обработка ответа
$responseData = json_decode($response, true);
// Удаление временных файлов
foreach ($localFiles as $localFile) {
unlink($localFile);
}
return $responseData;
}
public function saveImagesToUploadServer($albumId, $server, $photosList, $hash)
{
// URL для запроса
$url = 'https://api.vk.com/method/photos.save';
// Подготовка данных для отправки
$postFields = [
'album_id' => $albumId,
'server' => $server,
'group_id' => env('PUBLIC_ID'),
'photos_list' => $photosList, // Преобразуем массив в JSON-строку
'hash' => $hash,
'access_token' => $this->vkAuthService->getToken()->access_token,
'v' => '5.131', // Версия API
];
// Инициализация cURL
$ch = curl_init();
// Установка параметров cURL
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
// Выполнение запроса
$response = curl_exec($ch);
// Проверка на ошибки
if (curl_errno($ch)) {
return 'Ошибка cURL: ' . curl_error($ch);
}
// Закрытие cURL
curl_close($ch);
// Обработка ответа
$responseData = json_decode($response, true);
return $responseData;
}
public function createAlbum(string $title)
{
try {
return $this->vk->photos()->createAlbum($this->wallToken, array(
return $this->vk->photos()->createAlbum($this->vkAuthService->getToken()->access_token, array(
'title' => $title,
'group_id' => $this->publicId,
'privacy' => 0,
@@ -46,4 +150,20 @@ class VkAlbumService
}
}
public function deleteAlbum(int $album_id, int $group_id)
{
try {
return $this->vk->photos()->deleteAlbum($this->vkAuthService->getToken()->access_token, array(
'album_id' => $album_id,
'group_id' => $group_id,
));
} catch (\Exception $e) {
Log::error('Ошибка при удалении альбома: ' . $e->getMessage());
return [
'success' => false,
'message' => 'Не удалось удалить альбом: ' . $e->getMessage(),
];
}
}
}
+220
View File
@@ -0,0 +1,220 @@
<?php
namespace App\Services\VK;
use App\Services\VK\Album\VkAlbumService;
use App\Services\VK\Wall\VkWallService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use VK\Client\VKApiClient;
class VkAuthService
{
public function getToken()
{
try {
// Получаем последний токен
$token = DB::table('vk_tokens')->latest()->first();
// Проверяем, существует ли токен и является ли он валидным
if ($token && !$this->isTokenValid($token)) {
// Если токен не валиден, обновляем его
return $this->refresh();
}
return $token;
} catch (\Exception $e) {
// Обработка ошибок, например, логирование
Log::error('Ошибка при получении токена: ' . $e->getMessage());
return null; // Или выбросьте исключение, если это необходимо
}
}
public function redirectToProvider()
{
$state = bin2hex(random_bytes(16)); // Генерация случайной строки состояния
session(['vk_state' => $state]);
$code_verifier = $this->generateCodeVerifier();
$code_challenge = $this->generateCodeChallenge($code_verifier);
session(['vk_code_verifier' => $code_verifier]);
$url = 'https://id.vk.com/authorize?' . http_build_query([
'response_type' => 'code',
'client_id' => env('VK_APP_ID'),
'redirect_uri' => env('VK_REDIRECT_URI'),
'state' => $state,
'scope' => 'photos wall', // Укажите необходимые права доступа
'code_challenge' => $code_challenge, // Добавьте код, если используете PKCE
'code_challenge_method' => 's256',
]);
return redirect($url);
}
public function handleProviderCallback(Request $request)
{
$this->validateState($request);
$codeVerifier = session('vk_code_verifier');
$tokenData = $this->exchangeCodeForTokens($request, $codeVerifier);
if (isset($tokenData->error)) {
return response()->json(['error' => $tokenData->error_description], 400);
}
return $this->storeTokenData($tokenData, $request);
}
public function refresh()
{
$token = DB::table('vk_tokens')->latest()->first();
$newTokenData = $this->refreshToken($token->refresh_token, bin2hex(random_bytes(16)), $token->device_id);
return $this->storeRefreshTokenData($newTokenData['data']);
}
public function logout()
{
session()->forget('vk_state');
session()->forget('vk_code_verifier');
return redirect('/'); // Перенаправление на главную страницу
}
private function exchangeCodeForTokens(Request $request, $codeVerifier)
{
return Http::asForm()->post('https://id.vk.com/oauth2/auth', [
'grant_type' => 'authorization_code',
'code_verifier' => $codeVerifier,
'redirect_uri' => env('VK_REDIRECT_URI_AFTER_AUTH'),
'code' => $request->code,
'client_id' => env('VK_APP_ID'),
'device_id' => $request->device_id,
'state' => $request->state,
'scope' => 'photos wall',
])->object();
}
private function refreshToken($refresh_token, $state, $device_id)
{
$response = Http::asForm()->post('https://id.vk.com/oauth2/auth', [
'grant_type' => 'refresh_token',
'refresh_token' => $refresh_token,
'client_id' => env('VK_APP_ID'),
'device_id' => $device_id,
'state' => $state,
'scope' => 'photos wall',
]);
if ($response->failed()) {
// Обработка ошибок
return [
'success' => false,
'error' => $response->json(), // Возвращаем детали ошибки
];
}
return [
'success' => true,
'data' => $response->object(), // Успешный ответ
];
}
private function storeTokenData($tokenData, Request $request)
{
try {
DB::table('vk_tokens')->updateOrInsert(
['user_id' => $tokenData->user_id],
[
'user_id' => $tokenData->user_id,
'access_token' => $tokenData->access_token,
'refresh_token' => $tokenData->refresh_token,
'id_token' => $tokenData->id_token,
'state' => $tokenData->state,
'scope' => $tokenData->scope,
'device_id' => $request->device_id,
'token_expire' => Carbon::createFromTimestamp(Carbon::now()->timestamp + $tokenData->expires_in),
'updated_at' => now(),
]
);
} catch (\Exception $e) {
Log::error('Ошибка при обновлении или вставке токена: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Произошла ошибка при обновлении или создания токена.',
'error_message' => $e->getMessage()
], 500);
}
return redirect('/'); // Перенаправление после успешной авторизации
}
private function storeRefreshTokenData($tokenData)
{
try {
// Обновляем запись и получаем количество затронутых строк
$updatedRows = DB::table('vk_tokens')->where('user_id', $tokenData->user_id)->update(
[
'access_token' => $tokenData->access_token,
'refresh_token' => $tokenData->refresh_token,
'state' => $tokenData->state,
'scope' => $tokenData->scope,
'token_expire' => Carbon::createFromTimestamp(Carbon::now()->timestamp + $tokenData->expires_in),
'updated_at' => now(),
]
);
// Если обновление прошло успешно, получаем обновленную запись
if ($updatedRows > 0) {
return DB::table('vk_tokens')->where('user_id', $tokenData->user_id)->first();
}
// Если запись не найдена, можно вернуть null или выбросить исключение
return response()->json([
'success' => false,
'message' => 'Запись не найдена для обновления.',
], 404);
} catch (\Exception $e) {
Log::error('Ошибка при обновлении токена: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Произошла ошибка при обновлении токена.',
'error_message' => $e->getMessage()
], 500);
}
}
private function generateCodeVerifier($length = 128)
{
return bin2hex(random_bytes($length / 2));
}
private function generateCodeChallenge($code_verifier)
{
return rtrim(strtr(base64_encode(hash('sha256', $code_verifier, true)), '+/', '-_'), '=');
}
private function validateState(Request $request)
{
if ($request->input('state') !== session('vk_state')) {
abort(403, 'Invalid state');
}
}
private function isTokenValid($token)
{
if (Carbon::now() > $token->token_expire) {
return false;
}
return true;
}
}
+64 -5
View File
@@ -10,9 +10,12 @@ class VkService
{
protected VkWallService $wallService;
protected VkAlbumService $albumService;
public function __construct(VKApiClient $vk) {
protected int $public_id;
public function __construct() {
$vk = new VKApiClient();
$this->wallService = new VkWallService($vk);
$this->albumService = new VkAlbumService($vk);
$this->public_id = env('PUBLIC_ID');
}
public function getPosts(int $count = 10)
@@ -20,14 +23,70 @@ class VkService
return $this->wallService->getPosts($count);
}
public function createPost(string $message, int $from_group = 1)
public function getPostById(int $id)
{
return $this->wallService->createPost($message, $from_group);
return $this->wallService->getPostById($id);
}
public function createAlbum(string $title)
public function createPost(string $title, string $message, array $images = [], int $publish_date = null)
{
return $this->albumService->createAlbum($title);
$from_group = 1;
$album_attachment = '';
if ($images !== []) {
$album = $this->createAlbum($title, $images);
$album_attachment = $this->createAlbumAttachmentParam($album['id']);
}
return $this->wallService->createPost($message, $from_group, $album_attachment, $publish_date);
}
public function updatePost(int $id, string $title, string $message, array $images = [], int $publish_date = null)
{
$from_group = 1;
$vk_post = $this->wallService->getPostById($id);
$album_attachment = '';
if ($vk_post['attachments']) {
if ($this->getAlbumAttachment($vk_post['attachments'])) {
$album = $this->getAlbumAttachment($vk_post['attachments']);
$this->albumService->deleteAlbum($album['album']['id'], $this->public_id);
}
}
if ($images !== []) {
$album = $this->createAlbum($title, $images);
$album_attachment = $this->createAlbumAttachmentParam($album['id']);
}
return $this->wallService->updatePost($id, $message, $from_group, $album_attachment, $publish_date);
}
public function createAlbum(string $title, $images)
{
$album = $this->albumService->createAlbum($title);
$uploadServer = $this->albumService->getServerForUploadImages($album['id'], env('PUBLIC_ID'));
foreach (array_chunk($images, 4) as $images_slice) {
$images_data = $this->albumService->uploadImagesToUploadServer($uploadServer['upload_url'], $images_slice);
$this->albumService->saveImagesToUploadServer(
$images_data['aid'],
$images_data['server'],
$images_data['photos_list'],
$images_data['hash']
);
}
return $album;
}
private function createAlbumAttachmentParam(int $attachmentId)
{
return $this->wallService->generateAttachmentsParams('album', $attachmentId);
}
private function getAlbumAttachment(array $attachments)
{
$data = collect($attachments);
$filteredAttachment = $data->filter(function($attachment) {
return $attachment['type'] === 'album';
});
return (isset($filteredAttachment[0]) ? $filteredAttachment[0] : null);
}
}
+92 -7
View File
@@ -2,6 +2,10 @@
namespace App\Services\VK\Wall;
use App\Services\VK\VkAuthService;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use VK\Client\VKApiClient;
@@ -19,6 +23,8 @@ class VkWallService
$this->serviceToken = env('SERVICE_ACCESS_VK_KEY');
$this->publicId = env('PUBLIC_ID');
$this->publicDomain = env('PUBLIC_DOMAIN');
$this->vkAuthService = new VkAuthService();
}
public function getPosts(int $count)
@@ -30,15 +36,40 @@ class VkWallService
));
}
public function createPost(string $message, int $from_group, string $attachments = '')
public function getPostById(int $id)
{
$post = $this->vk->wall()->getById($this->serviceToken, array(
'posts' => '-'. $this->publicId . '_' . $id,
));
return $post[0];
}
public function createPost(string $message, int $from_group, string $attachments = '', int $publish_date)
{
$params = [
'owner_id' => '-' . $this->publicId,
'from_group' => $from_group,
'message' => $message,
'attachments' => $attachments,
'access_token' => $this->wallToken,
'publish_date' => $publish_date,
'v' => '5.131',
];
try {
return $this->vk->wall()->post($this->wallToken, array(
'owner_id' => '-' . $this->publicId,
'from_group' => $from_group,
'message' => $message,
'attachments' => $attachments
));
$response = Http::asForm()->post('https://api.vk.com/method/wall.post', $params);
if ($response->successful() && isset($response['response'])) {
return [
'success' => true,
'post_id' => $response['response']['post_id'],
];
} else {
throw new \Exception('Ошибка API: ' . json_encode($response->json()));
}
} catch (\Exception $e) {
Log::error('Ошибка при создании поста: ' . $e->getMessage());
return [
@@ -47,4 +78,58 @@ class VkWallService
];
}
}
public function updatePost(int $post_id, string $message = '', int $from_group = 1, string $attachments = '', int $publish_date)
{
if (empty($message) && empty($attachments)) {
return [
'success' => false,
'message' => 'Необходимо указать либо сообщение, либо вложения.',
];
}
$params = [
'owner_id' => '-' . $this->publicId,
'post_id' => $post_id,
'message' => $message,
'attachments' => $attachments,
'access_token' => $this->wallToken,
'v' => '5.131',
];
try {
return $this->vk->wall()->edit(
$this->vkAuthService->getToken()->access_token,
array(
'owner_id' => '-' . $this->publicId,
'post_id' => $post_id,
'message' => $message,
'attachments' => $attachments,
'publish_date' => $publish_date,
),
);
} catch (\Exception $e) {
Log::error('Ошибка при обновлении новости SDK: ' . $e->getMessage());
return [
'success' => false,
'message' => 'Не удалось обновить новость SDK: ' . $e->getMessage(),
];
}
}
public function generateAttachmentsParams(string $attachmentType, int $attachmentId)
{
$pubic_id = env('PUBLIC_ID');
switch ($attachmentType) {
case 'album': {
return "album-{$pubic_id}_{$attachmentId}";
}
case 'doc': {
}
}
}
}