diff --git a/README.md b/README.md index b8408ba..239d9eb 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,5 @@ Работа с проектом: >git pull ->php artisan migrate \ No newline at end of file +>php artisan vendor:publish --provider="Te7aHoudini\LaravelTrix\LaravelTrixServiceProvider" +>php artisan migrate diff --git a/app/Console/Commands/CreateModelAndMigrationCommand.php b/app/Console/Commands/CreateModelAndMigrationCommand.php index 25ab7bd..424febf 100644 --- a/app/Console/Commands/CreateModelAndMigrationCommand.php +++ b/app/Console/Commands/CreateModelAndMigrationCommand.php @@ -75,7 +75,7 @@ private function createPolicy() $policy = Str::singular(class_basename($this->option('create_policy'))); $model = Str::singular(class_basename($this->option('model'))); $this->call('make:policy', [ - 'name' => 'App\\Modules\\' . $path . '\\Policies\\' . $policy, + 'name' => 'App\\Modules\\' . $path . '\\Http\\Policies\\' . $policy, '--model' => $model ]); } diff --git a/app/Modules/Admin/Http/Controllers/AdminPostsController.php b/app/Modules/Admin/Http/Controllers/AdminPostsController.php index b563988..66227cc 100644 --- a/app/Modules/Admin/Http/Controllers/AdminPostsController.php +++ b/app/Modules/Admin/Http/Controllers/AdminPostsController.php @@ -38,6 +38,7 @@ public function create() public function store(Request $request) { + $request['text'] = 'none'; $validated = $request->validate([ 'name' => 'required', 'category' => 'required', @@ -64,7 +65,7 @@ public function store(Request $request) $path = $request->file('imageFile')->store('posts', ['disk' => 'public']); $request['image'] = $path; $post = Post::create( - $request->only(['name', 'short_text', 'text', 'category', 'image']) + $request->only(['name', 'short_text', 'text', 'category', 'image', 'post-trixFields']) ); if (array_key_exists('cities', $request->all()) && is_array($request['cities'])) { foreach ($request->cities as $cityId) { @@ -87,11 +88,14 @@ public function edit(Post $post) public function update(Request $request, Post $post) { + $request['text'] = 'none'; + $validated = $request->validate([ 'name' => 'required', 'category' => 'required', 'short_text' => 'max:500', 'text' => 'required', + 'imageFile' => 'mimes:jpg,bmp,png' ]); if ($request->file('imageFile')) { @@ -100,10 +104,11 @@ public function update(Request $request, Post $post) } else { $reuqest['image'] = $post->image; } + $post = $post->update( - $request->only(['name', 'short_text', 'text', 'category', 'image']) + $request->only(['name', 'short_text', 'text', 'category', 'image', 'post-trixFields']) ); - return to_route('admin.posts'); + return back()->withSuccess('Новость обновлена успешно'); } public function delete(Post $post) diff --git a/app/Modules/Main/Http/Policies/ContentPolicy.php b/app/Modules/Main/Http/Policies/ContentPolicy.php new file mode 100644 index 0000000..a6a585c --- /dev/null +++ b/app/Modules/Main/Http/Policies/ContentPolicy.php @@ -0,0 +1,36 @@ +isAdmin()) { + return true; + } + + return null; + } + + /** + * Determine whether the user can CRUD content (posts, agents, documents) + */ + public function manageContent(User $user, City $city): bool + { + if (!$cityManagersOfUser = CityManager::where('user_id', $user->id)->get()) { + return false; + } + foreach ($cityManagersOfUser as $cityManager) { + if ($cityManager->city_id == $city->id) { + return true; + } + } + return false; + } +} diff --git a/app/Modules/Main/Models/Scopes/CityScope.php b/app/Modules/Main/Models/Scopes/CityScope.php index cd73ad7..152f247 100644 --- a/app/Modules/Main/Models/Scopes/CityScope.php +++ b/app/Modules/Main/Models/Scopes/CityScope.php @@ -14,10 +14,7 @@ public function apply(Builder $builder, Model $model): void { //dd(GetAvailableCities()); if ($cities = GetAvailableCities()) { //получаю доступные пользователю города - $citiesIds = []; - foreach ($cities as $city) { - $citiesIds[] = $city->id; - } + $citiesIds = $cities->pluck('id')->toArray(); $builder->whereHas('cities', fn($q) => $q->whereIn('cities.id', $citiesIds)); $builder->OrDoesntHave('cities'); } else { diff --git a/app/Modules/Main/Providers/ModuleServiceProvider.php b/app/Modules/Main/Providers/ModuleServiceProvider.php index 300fa9c..7d8479a 100644 --- a/app/Modules/Main/Providers/ModuleServiceProvider.php +++ b/app/Modules/Main/Providers/ModuleServiceProvider.php @@ -93,6 +93,7 @@ protected function registerComponent() protected function registerPolicies() { Gate::policy(\Modules\Main\Models\Company\Company::class, \Modules\Main\Http\Policies\CompanyPolicy::class); + Gate::policy(\Modules\Main\Models\City::class, \Modules\Main\Http\Policies\ContentPolicy::class); } protected function registerHelpers() diff --git a/app/Modules/Post/Http/Livewire/PostCreator.php b/app/Modules/Post/Http/Livewire/PostCreator.php deleted file mode 100644 index 81c52bd..0000000 --- a/app/Modules/Post/Http/Livewire/PostCreator.php +++ /dev/null @@ -1,106 +0,0 @@ -dispatch('phoneInputAdded'); - - } - public function resetData() - { - $this->mount(); - } - public function back() - { - $this->status = FormStatus::IN_PROCESS; - } - - public function save() - { - $hasErrors = false; - foreach ($this->selectedObjects as $complexId=>$room) { - if ($this->createDeal($complexId, $room)) { - unset($this->selectedObjects[$complexId]); - } else { - $hasErrors = true; - } - } - if ($hasErrors) { - return $this->status = FormStatus::ERROR; - } - $this->status = FormStatus::SUCCESS; - } - - private function createDeal($complexId, $room) - { - if ( - !$deal = Deal::create([ - 'agent_id' => $this->agentId, - 'complex_id' => $complexId, - 'plan7_data' => (is_array($room) && array_key_exists('id', $room)) ? json_encode($room) : null - ]) - ) { - return false; - } - - foreach ($this->contacts as $contact) { - if ( - !$newUser = Client::updateOrCreate( - ['phone' => $contact['phones'][0]], - [ - 'name' => trim($contact['firstName'] . ' ' . $contact['secondName']), - 'phone' => $contact['phones'][0] - ] - ) - ) { - return false; - } - if ( - !$dealClient = DealClients::firstOrCreate([ - 'deal_id' => $deal->id, - 'client_id' => $newUser->id - ]) - ) { - return false; - } - } - $this->dispatch('clientCreated'); - return true; - } -} \ No newline at end of file diff --git a/app/Modules/Post/Http/Livewire/PostUpdate.php b/app/Modules/Post/Http/Livewire/PostUpdate.php new file mode 100644 index 0000000..0752cfb --- /dev/null +++ b/app/Modules/Post/Http/Livewire/PostUpdate.php @@ -0,0 +1,32 @@ +categories = \Modules\Post\Models\PostCategory::cases(); + $this->post = $post; + $this->availableCities = GetAvailableCities(); + } + public function render() + { + return view( + 'post::form.update' + ); + } + public function rendered() + { + + } +} \ No newline at end of file diff --git a/app/Modules/Post/Http/Livewire/PostsList.php b/app/Modules/Post/Http/Livewire/PostsList.php index d44bad1..55b7d50 100644 --- a/app/Modules/Post/Http/Livewire/PostsList.php +++ b/app/Modules/Post/Http/Livewire/PostsList.php @@ -22,7 +22,7 @@ public function open($id) } public function render() { - $posts = Post::orderBy('id', 'desc'); + $posts = Post::orderBy('id', 'desc')->visibleForUser(); if ($this->category && $this->category != 'all') $posts = $posts->where('category', $this->category); diff --git a/app/Modules/Post/Models/Post.php b/app/Modules/Post/Models/Post.php index d8b5c30..2be7a48 100644 --- a/app/Modules/Post/Models/Post.php +++ b/app/Modules/Post/Models/Post.php @@ -6,38 +6,52 @@ use Modules\Post\Models\PostCity; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasMany; +use Te7aHoudini\LaravelTrix\Traits\HasTrixRichText; use Modules\Main\Models\Scopes\CityScope; class Post extends Model { use HasFactory; - + use HasTrixRichText; + protected $guarded = []; protected $fillable = [ 'name', 'short_text', 'text', 'category', - 'image' + 'image', + 'post-trixFields' ]; protected static function booted(): void { static::addGlobalScope(new CityScope); + + static::deleted(function ($post) { + $post->trixRichText->each->delete(); + $post->trixAttachments->each->purge(); + }); } public function cities(): HasManyThrough { return $this->hasManyThrough( - City::class, + City::class, PostCity::class, 'post_id', 'id', 'id', 'city_id' - ); + ); + } + + public function scopeVisibleForUser(Builder $query) + { + auth()->user()->can('view', $this) ? $query : $query->whereRaw('0 = 1'); } } diff --git a/app/Modules/Post/Policies/PostPolicy.php b/app/Modules/Post/Policies/PostPolicy.php index 24bfbb5..25c4988 100644 --- a/app/Modules/Post/Policies/PostPolicy.php +++ b/app/Modules/Post/Policies/PostPolicy.php @@ -26,17 +26,23 @@ public function viewAny(User $user): bool } /** - * Determine whether the user can view the model. + * Проврка возможности просмотра новости. + * Если пользователь не состоит ни в одной из ролей ни в одном из городов, то false + * Далее проверка, если новость привязана к городам и у пользователя нет пересечений по этим городам, то false */ public function view(User $user, Post $post): bool { + $availableCities = GetAvailableCities()->pluck('id')->toArray(); + if (!$availableCities) { + return false; + } + if ($postCities = $post->cities->pluck('id')->toArray()) { - $availableCities = GetAvailableCities()->pluck('id')->toArray(); - if (array_intersect($postCities, $availableCities)) { - return true; + if (!array_intersect($postCities, $availableCities)) { + return false; } } - return false; + return true; } /** diff --git a/app/Modules/Post/Providers/ModuleServiceProvider.php b/app/Modules/Post/Providers/ModuleServiceProvider.php index 2afcc28..cc11c9a 100644 --- a/app/Modules/Post/Providers/ModuleServiceProvider.php +++ b/app/Modules/Post/Providers/ModuleServiceProvider.php @@ -69,7 +69,7 @@ protected function registerLivewire() { Livewire::component('posts.list', \Modules\Post\Http\Livewire\PostsList::class); Livewire::component('post.card', \Modules\Post\Http\Livewire\PostCard::class); - Livewire::component('post.form', \Modules\Post\Http\Livewire\PostCreator::class); + Livewire::component('post.form', \Modules\Post\Http\Livewire\PostUpdate::class); } protected function registerComponent() diff --git a/app/Modules/Post/Views/form/update.blade.php b/app/Modules/Post/Views/form/update.blade.php new file mode 100644 index 0000000..5b48eae --- /dev/null +++ b/app/Modules/Post/Views/form/update.blade.php @@ -0,0 +1,93 @@ +
+ @if($post->id) Редактировать @else Новая публикация @endif + @if($isActive == 'active') + + + @endif +
\ No newline at end of file diff --git a/app/Modules/Post/Views/index.blade.php b/app/Modules/Post/Views/index.blade.php index 196c32e..af564ec 100644 --- a/app/Modules/Post/Views/index.blade.php +++ b/app/Modules/Post/Views/index.blade.php @@ -1,63 +1,70 @@ @extends('layouts.app') @section('content') - -
-
-
-
- - + td, + h4 { + color: #333c4e !important + } + +
+
+ +
+ + - @foreach ($categories as $category) - value ? 'checked' : '' }}> - - @endforeach + @foreach ($categories as $category) + value ? 'checked' : '' }}> + + @endforeach +
+ +
+ @can('create', \Modules\Post\Models\Post::class) +
+ @livewire('post.form', ['post' => new \Modules\Post\Models\Post()])
+ @endcan + + +
-
- - -
+ +
- -
+
+ @livewire('posts.list', $filter) +
+ -
- @livewire('posts.list', $filter) -
- -
-@endsection + +
+@endsection \ No newline at end of file diff --git a/app/Modules/Post/Views/list/card.blade.php b/app/Modules/Post/Views/list/card.blade.php index eb9298c..2634ebf 100644 --- a/app/Modules/Post/Views/list/card.blade.php +++ b/app/Modules/Post/Views/list/card.blade.php @@ -20,8 +20,13 @@
@endif

{{ $post->name }}

+ @can('update', $post) +
+ @livewire('post.form', ['post' => $post]) +
+ @endcan

- {!! $post->text !!} + {!! $post->trixRender('content') !!}

diff --git a/composer.json b/composer.json index 0cdd6c3..140551b 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "laravel/scout": "^11.1", "laravel/tinker": "^2.8", "laravel/ui": "^4.5", - "livewire/livewire": "^3.5" + "livewire/livewire": "^3.5", + "te7a-houdini/laravel-trix": "^3.1" }, "require-dev": { "fakerphp/faker": "^1.9.1", diff --git a/composer.lock b/composer.lock index 4243474..28acdcb 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": "ea5a7c49862ceb1f5b573b695143a176", + "content-hash": "71bff85f2be8492b6394f7265f7c4ccf", "packages": [ { "name": "bacon/bacon-qr-code", @@ -5927,6 +5927,63 @@ ], "time": "2025-09-25T15:37:27+00:00" }, + { + "name": "te7a-houdini/laravel-trix", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/amaelftah/laravel-trix.git", + "reference": "e8ff1c09420113b1bc40d02f95fc4b475349ca5d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amaelftah/laravel-trix/zipball/e8ff1c09420113b1bc40d02f95fc4b475349ca5d", + "reference": "e8ff1c09420113b1bc40d02f95fc4b475349ca5d", + "shasum": "" + }, + "require": { + "laravel/framework": "~5.8.0|~6.0|~7.0|~8.0|~9.0|^10.0|^11.0|^12.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^3.5|~4.0|~5.0|~6.0|^8.0|^9.0|^10.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Te7aHoudini\\LaravelTrix\\LaravelTrixServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Te7aHoudini\\LaravelTrix\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ahmed Abd El Ftah", + "email": "ahmedabdelftah95165@gmail.com", + "role": "Developer" + } + ], + "description": "trix editor for laravel inspired by ActionText for rails", + "homepage": "https://github.com/te7ahoudini/laravel-trix", + "keywords": [ + "laravel-trix", + "te7a-houdini" + ], + "support": { + "issues": "https://github.com/amaelftah/laravel-trix/issues", + "source": "https://github.com/amaelftah/laravel-trix/tree/3.1.0" + }, + "time": "2025-02-18T17:13:43+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.3.0", diff --git a/config/laravel-trix.php b/config/laravel-trix.php new file mode 100644 index 0000000..0915b01 --- /dev/null +++ b/config/laravel-trix.php @@ -0,0 +1,9 @@ + env('LARAVEL_TRIX_STORAGE_DISK', 'public'), + + 'store_attachment_action' => Te7aHoudini\LaravelTrix\Http\Controllers\TrixAttachmentController::class.'@store', + + 'destroy_attachment_action' => Te7aHoudini\LaravelTrix\Http\Controllers\TrixAttachmentController::class.'@destroy', +]; diff --git a/database/migrations/2026_05_08_031808_create_trix_rich_texts_table.php b/database/migrations/2026_05_08_031808_create_trix_rich_texts_table.php new file mode 100644 index 0000000..c828d5a --- /dev/null +++ b/database/migrations/2026_05_08_031808_create_trix_rich_texts_table.php @@ -0,0 +1,46 @@ +unsignedBigInteger('id')->autoIncrement(); + $table->string('field'); + $table->morphs('model'); + $table->text('content')->nullable(); + $table->timestamps(); + }); + + Schema::create('trix_attachments', function (Blueprint $table) { + $table->unsignedBigInteger('id')->autoIncrement(); + $table->string('field'); + $table->unsignedInteger('attachable_id')->nullable(); + $table->string('attachable_type'); + $table->string('attachment'); + $table->string('disk'); + $table->boolean('is_pending')->default(1); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('trix_attachments'); + Schema::drop('trix_rich_texts'); + } +} \ No newline at end of file diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index a24684c..4a56d01 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -15,6 +15,7 @@ @vite(['resources/sass/app.scss', 'resources/js/app.js', 'resources/js/multiselect.js', 'resources/js/zoomable.js', 'resources/css/app.css', 'resources/css/form.css', 'resources/css/docs.css', 'resources/css/plan7.css', 'resources/css/zoomable.css', 'resources/css/multiselect.css']) @include('layouts.design') + @trixassets