-
- |
+ @if($project->is_public)
+ {{ __('Yes') }}
+ @else
+ {{ __('No') }}
+ @endif
+ |
+
+
+
+
+
+
@can('delete', $project)
-
@endcan
diff --git a/app/application/resources/views/admin/projects/show.blade.php b/app/application/resources/views/admin/projects/show.blade.php
new file mode 100644
index 0000000..119d2a6
--- /dev/null
+++ b/app/application/resources/views/admin/projects/show.blade.php
@@ -0,0 +1,32 @@
+@section('meta_title', __('admin-sections.Project') . ': ' . $project->name)
+@section('h1', __('admin-sections.Project') . ': ' . $project->name)
+
+ @include('admin.projects._top')
+
+
diff --git a/app/application/resources/views/components/volt/forms/textarea-wysiwyg.blade.php b/app/application/resources/views/components/volt/forms/textarea-wysiwyg.blade.php
new file mode 100644
index 0000000..32ddf08
--- /dev/null
+++ b/app/application/resources/views/components/volt/forms/textarea-wysiwyg.blade.php
@@ -0,0 +1,24 @@
+
+
+
+ @error($requestName)
+ {{ $message }}
+ @enderror
+
+@pushOnce('scripts')
+
+
+@endpushonce
diff --git a/app/application/resources/views/components/volt/forms/textarea.blade.php b/app/application/resources/views/components/volt/forms/textarea.blade.php
new file mode 100644
index 0000000..0be825c
--- /dev/null
+++ b/app/application/resources/views/components/volt/forms/textarea.blade.php
@@ -0,0 +1,7 @@
+
+
+
+ @error($requestName)
+ {{ $message }}
+ @enderror
+
diff --git a/app/application/routes/web.php b/app/application/routes/web.php
index 1107c8e..9e6b87d 100644
--- a/app/application/routes/web.php
+++ b/app/application/routes/web.php
@@ -18,7 +18,14 @@
Route::resource('roles', \App\Http\Controllers\Admin\RolesController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['role' => '[0-9]+']);
- Route::resource('projects', \App\Http\Controllers\Admin\ProjectsController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['project' => '[0-9]+']);
+ Route::resource('projects', \App\Http\Controllers\Admin\ProjectsController::class)->where(['project' => '[0-9]+']);
+ Route::prefix('projects/{project}')->as('projects.')->group(function () {
+
+ Route::get('about', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'languages'])->name('about.languages');
+ Route::get('about/{language}', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'edit'])->name('about.edit')->where(['language' => '[0-9]+']);
+ Route::put('about/{language}', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'update'])->name('about.update')->where(['language' => '[0-9]+']);
+
+ })->where(['project' => '[0-9]+']);
Route::post('languages/new-language', [\App\Http\Controllers\Admin\LanguagesController::class, 'newLanguage'])->name('new-language');
});
diff --git a/app/application/vite.config.js b/app/application/vite.config.js
index 1563b46..dd794b1 100644
--- a/app/application/vite.config.js
+++ b/app/application/vite.config.js
@@ -1,14 +1,23 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
+import { viteStaticCopy } from 'vite-plugin-static-copy'
export default defineConfig({
plugins: [
laravel({
input: [
'resources/volt/scss/app.scss',
- 'resources/volt/js/app.js'
+ 'resources/volt/js/app.js',
],
refresh: true,
}),
+ viteStaticCopy({
+ targets: [
+ {
+ src: ['node_modules/tinymce/*', 'resources/tinymce/*'],
+ dest: 'tinymce'
+ }
+ ]
+ })
],
});
From 4583908230b5ee34b7cf31c13514cd152a484f1b Mon Sep 17 00:00:00 2001
From: Leonid Nikitin
Date: Sun, 14 Apr 2024 15:29:00 +0500
Subject: [PATCH 07/20] Added package barryvdh/laravel-debugbar.
---
app/application/composer.json | 1 +
app/application/composer.lock | 154 +++++++++++++++++++-
app/application/storage/debugbar/.gitignore | 2 +
3 files changed, 156 insertions(+), 1 deletion(-)
create mode 100755 app/application/storage/debugbar/.gitignore
diff --git a/app/application/composer.json b/app/application/composer.json
index 6f27f4e..71e3603 100644
--- a/app/application/composer.json
+++ b/app/application/composer.json
@@ -12,6 +12,7 @@
"laravel/tinker": "^2.9"
},
"require-dev": {
+ "barryvdh/laravel-debugbar": "^3.13",
"fakerphp/faker": "^1.23",
"laravel-lang/common": "^6.2",
"laravel/pint": "^1.13",
diff --git a/app/application/composer.lock b/app/application/composer.lock
index 5b53c28..0b3bc9f 100644
--- a/app/application/composer.lock
+++ b/app/application/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": "69cad3ad961845edc7e2ac7c0f9fb5e6",
+ "content-hash": "5e3226c3090c81571c830e2c383433ab",
"packages": [
{
"name": "brick/math",
@@ -5994,6 +5994,90 @@
},
"time": "2024-01-28T17:52:47+00:00"
},
+ {
+ "name": "barryvdh/laravel-debugbar",
+ "version": "v3.13.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/barryvdh/laravel-debugbar.git",
+ "reference": "00201bcd1eaf9b1d3debddcdc13c219e4835fb61"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/00201bcd1eaf9b1d3debddcdc13c219e4835fb61",
+ "reference": "00201bcd1eaf9b1d3debddcdc13c219e4835fb61",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/routing": "^9|^10|^11",
+ "illuminate/session": "^9|^10|^11",
+ "illuminate/support": "^9|^10|^11",
+ "maximebf/debugbar": "~1.22.0",
+ "php": "^8.0",
+ "symfony/finder": "^6|^7"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.3.3",
+ "orchestra/testbench-dusk": "^5|^6|^7|^8|^9",
+ "phpunit/phpunit": "^9.6|^10.5",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.13-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Barryvdh\\Debugbar\\ServiceProvider"
+ ],
+ "aliases": {
+ "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar"
+ }
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/helpers.php"
+ ],
+ "psr-4": {
+ "Barryvdh\\Debugbar\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "PHP Debugbar integration for Laravel",
+ "keywords": [
+ "debug",
+ "debugbar",
+ "laravel",
+ "profiler",
+ "webprofiler"
+ ],
+ "support": {
+ "issues": "https://github.com/barryvdh/laravel-debugbar/issues",
+ "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.4"
+ },
+ "funding": [
+ {
+ "url": "https://fruitcake.nl",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/barryvdh",
+ "type": "github"
+ }
+ ],
+ "time": "2024-04-10T09:15:45+00:00"
+ },
{
"name": "composer/semver",
"version": "3.4.0",
@@ -7463,6 +7547,74 @@
},
"time": "2024-03-20T20:09:31+00:00"
},
+ {
+ "name": "maximebf/debugbar",
+ "version": "v1.22.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/maximebf/php-debugbar.git",
+ "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96",
+ "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2|^8",
+ "psr/log": "^1|^2|^3",
+ "symfony/var-dumper": "^4|^5|^6|^7"
+ },
+ "require-dev": {
+ "dbrekelmans/bdi": "^1",
+ "phpunit/phpunit": "^8|^9",
+ "symfony/panther": "^1|^2.1",
+ "twig/twig": "^1.38|^2.7|^3.0"
+ },
+ "suggest": {
+ "kriswallsmith/assetic": "The best way to manage assets",
+ "monolog/monolog": "Log using Monolog",
+ "predis/predis": "Redis storage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.22-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "DebugBar\\": "src/DebugBar/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Maxime Bouroumeau-Fuseau",
+ "email": "maxime.bouroumeau@gmail.com",
+ "homepage": "http://maximebf.com"
+ },
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "Debug bar in the browser for php application",
+ "homepage": "https://github.com/maximebf/php-debugbar",
+ "keywords": [
+ "debug",
+ "debugbar"
+ ],
+ "support": {
+ "issues": "https://github.com/maximebf/php-debugbar/issues",
+ "source": "https://github.com/maximebf/php-debugbar/tree/v1.22.3"
+ },
+ "time": "2024-04-03T19:39:26+00:00"
+ },
{
"name": "mockery/mockery",
"version": "1.6.11",
diff --git a/app/application/storage/debugbar/.gitignore b/app/application/storage/debugbar/.gitignore
new file mode 100755
index 0000000..d6b7ef3
--- /dev/null
+++ b/app/application/storage/debugbar/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
From 63ea5dac9245bc6f457e3c25e3b075569843dabf Mon Sep 17 00:00:00 2001
From: Leonid Nikitin
Date: Mon, 15 Apr 2024 22:53:34 +0500
Subject: [PATCH 08/20] Added the ability to add links to the project.
---
.../app/Dto/Builder/ProjectLink.php | 10 +
.../Dto/Service/Admin/Project/Link/Index.php | 21 ++
.../Admin/Project/Link/StoreUpdate.php | 35 +++
app/application/app/Enums/Permission.php | 1 +
.../Admin/Projects/LinksController.php | 100 +++++++++
.../Admin/Projects/Links/IndexRequest.php | 29 +++
.../Projects/Links/StoreUpdateRequest.php | 38 ++++
app/application/app/Models/Project.php | 5 +
app/application/app/Models/ProjectLink.php | 46 ++++
.../app/Policies/ProjectLinkPolicy.php | 34 +++
.../Repositories/ProjectLinkRepository.php | 32 +++
.../Services/Admin/Project/LinkService.php | 203 ++++++++++++++++++
.../Services/ProjectLink/BuilderCommand.php | 15 ++
.../ProjectLink/ProjectLinkCommandHandler.php | 25 +++
app/application/lang/en.json | 6 +-
app/application/lang/en/admin-sections.php | 1 +
app/application/lang/en/permissions.php | 12 +-
app/application/lang/en/validation.php | 2 +
app/application/lang/ru.json | 6 +-
app/application/lang/ru/admin-sections.php | 1 +
app/application/lang/ru/permissions.php | 13 +-
app/application/lang/ru/validation.php | 2 +
.../admin/projects/links/_from.blade.php | 11 +
.../views/admin/projects/links/_top.blade.php | 8 +
.../admin/projects/links/create.blade.php | 17 ++
.../views/admin/projects/links/edit.blade.php | 18 ++
.../admin/projects/links/index.blade.php | 61 ++++++
.../views/admin/projects/show.blade.php | 14 +-
app/application/routes/web.php | 3 +-
29 files changed, 756 insertions(+), 13 deletions(-)
create mode 100644 app/application/app/Dto/Builder/ProjectLink.php
create mode 100644 app/application/app/Dto/Service/Admin/Project/Link/Index.php
create mode 100644 app/application/app/Dto/Service/Admin/Project/Link/StoreUpdate.php
create mode 100644 app/application/app/Http/Controllers/Admin/Projects/LinksController.php
create mode 100644 app/application/app/Http/Requests/Admin/Projects/Links/IndexRequest.php
create mode 100644 app/application/app/Http/Requests/Admin/Projects/Links/StoreUpdateRequest.php
create mode 100644 app/application/app/Models/ProjectLink.php
create mode 100644 app/application/app/Policies/ProjectLinkPolicy.php
create mode 100644 app/application/app/Repositories/ProjectLinkRepository.php
create mode 100644 app/application/app/Services/Admin/Project/LinkService.php
create mode 100644 app/application/app/Services/ProjectLink/BuilderCommand.php
create mode 100644 app/application/app/Services/ProjectLink/ProjectLinkCommandHandler.php
create mode 100644 app/application/resources/views/admin/projects/links/_from.blade.php
create mode 100644 app/application/resources/views/admin/projects/links/_top.blade.php
create mode 100644 app/application/resources/views/admin/projects/links/create.blade.php
create mode 100644 app/application/resources/views/admin/projects/links/edit.blade.php
create mode 100644 app/application/resources/views/admin/projects/links/index.blade.php
diff --git a/app/application/app/Dto/Builder/ProjectLink.php b/app/application/app/Dto/Builder/ProjectLink.php
new file mode 100644
index 0000000..4d858d4
--- /dev/null
+++ b/app/application/app/Dto/Builder/ProjectLink.php
@@ -0,0 +1,10 @@
+linkBuilderDto;
+ }
+}
diff --git a/app/application/app/Dto/Service/Admin/Project/Link/StoreUpdate.php b/app/application/app/Dto/Service/Admin/Project/Link/StoreUpdate.php
new file mode 100644
index 0000000..755719b
--- /dev/null
+++ b/app/application/app/Dto/Service/Admin/Project/Link/StoreUpdate.php
@@ -0,0 +1,35 @@
+title;
+ }
+
+ public function getLink(): string
+ {
+ return $this->link;
+ }
+
+ public function getSort(): int
+ {
+ return $this->sort;
+ }
+
+ public function getLanguageId(): ?int
+ {
+ return $this->languageId;
+ }
+}
diff --git a/app/application/app/Enums/Permission.php b/app/application/app/Enums/Permission.php
index c4e7416..bdf8d14 100644
--- a/app/application/app/Enums/Permission.php
+++ b/app/application/app/Enums/Permission.php
@@ -9,6 +9,7 @@ enum Permission: string
case User = 'user';
case Project = 'project';
case ProjectContent = 'project-content';
+ case ProjectLink = 'project-link';
public function getPermissions(): array
{
diff --git a/app/application/app/Http/Controllers/Admin/Projects/LinksController.php b/app/application/app/Http/Controllers/Admin/Projects/LinksController.php
new file mode 100644
index 0000000..c937267
--- /dev/null
+++ b/app/application/app/Http/Controllers/Admin/Projects/LinksController.php
@@ -0,0 +1,100 @@
+user();
+ $data = $request->getDto();
+ $querySettingsDto = new QuerySettingsDto(
+ limit: 20,
+ page: $data->getPage(),
+ queryWith: ['language']
+ );
+
+ $result = $this->linkService->index($projectId, $data->getLinkBuilderDto(), $querySettingsDto, $user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('admin/projects/links/index', $result->getData());
+ }
+
+ public function create(int $projectId, Request $request): View
+ {
+ $user = $request->user();
+ $result = $this->linkService->create($projectId, $user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('admin/projects/links/create', $result->getData());
+ }
+
+ public function edit(int $projectId, int $id, Request $request): View
+ {
+ $user = $request->user();
+ $result = $this->linkService->edit($projectId, $id, $user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('admin/projects/links/edit', $result->getData());
+ }
+
+ public function store(int $projectId, StoreUpdateRequest $request): RedirectResponse
+ {
+ $data = $request->getDto();
+ $user = $request->user();
+ $result = $this->linkService->store($projectId, $data, $user);
+ if ($result->isError()) {
+ return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
+ }
+
+ return redirect()->route('admin.projects.links.edit', [
+ 'project' => $projectId,
+ 'link' => $result->getModel()->id,
+ ])->withSuccess($result->getMessage());
+ }
+
+ public function update(int $projectId, int $id, StoreUpdateRequest $request): RedirectResponse
+ {
+ $data = $request->getDto();
+ $user = $request->user();
+ $result = $this->linkService->update($projectId, $id, $data, $user);
+ if ($result->isError()) {
+ return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
+ }
+
+ return redirect()->route('admin.projects.links.edit', [
+ 'project' => $projectId,
+ 'link' => $result->getModel()->id,
+ ])->withSuccess($result->getMessage());
+ }
+
+ public function destroy(int $projectId, int $id, Request $request): RedirectResponse
+ {
+ $user = $request->user();
+ $result = $this->linkService->destroy($projectId, $id, $user);
+ if ($result->isError()) {
+ return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
+ }
+
+ return redirect()->route('admin.projects.links.index', ['project' => $projectId])->withSuccess($result->getMessage());
+ }
+}
diff --git a/app/application/app/Http/Requests/Admin/Projects/Links/IndexRequest.php b/app/application/app/Http/Requests/Admin/Projects/Links/IndexRequest.php
new file mode 100644
index 0000000..b51ebe7
--- /dev/null
+++ b/app/application/app/Http/Requests/Admin/Projects/Links/IndexRequest.php
@@ -0,0 +1,29 @@
+ ['nullable', 'numeric', 'min:1']
+ ];
+ }
+
+ public function getDto(): Index
+ {
+ return new Index(
+ linkBuilderDto: new ProjectLink(),
+ page: (int) $this->input('page', 1),
+ );
+ }
+}
diff --git a/app/application/app/Http/Requests/Admin/Projects/Links/StoreUpdateRequest.php b/app/application/app/Http/Requests/Admin/Projects/Links/StoreUpdateRequest.php
new file mode 100644
index 0000000..e9ac344
--- /dev/null
+++ b/app/application/app/Http/Requests/Admin/Projects/Links/StoreUpdateRequest.php
@@ -0,0 +1,38 @@
+ ['required', 'string', 'max:255'],
+ 'link' => ['required', 'string', 'max:255', 'url:http,https'],
+ 'sort' => ['required', 'integer', 'min:-1000', 'max:1000'],
+ 'language_id' => ['nullable', 'integer', 'min:1'],
+ ];
+ }
+
+ public function getDto(): StoreUpdate
+ {
+ $languageId = $this->input('language_id');
+ if (! \is_null($languageId)) {
+ $languageId = (int) $languageId;
+ }
+
+ return new StoreUpdate(
+ title: $this->input('title'),
+ link: $this->input('link'),
+ sort: (int) $this->input('sort'),
+ languageId: $languageId,
+ );
+ }
+}
diff --git a/app/application/app/Models/Project.php b/app/application/app/Models/Project.php
index 6b94acb..5e2862b 100644
--- a/app/application/app/Models/Project.php
+++ b/app/application/app/Models/Project.php
@@ -52,4 +52,9 @@ public function contents(): HasMany
{
return $this->hasMany(ProjectContent::class);
}
+
+ public function links(): HasMany
+ {
+ return $this->hasMany(ProjectLink::class);
+ }
}
diff --git a/app/application/app/Models/ProjectLink.php b/app/application/app/Models/ProjectLink.php
new file mode 100644
index 0000000..738150f
--- /dev/null
+++ b/app/application/app/Models/ProjectLink.php
@@ -0,0 +1,46 @@
+ 100,
+ ];
+
+ protected $fillable = [
+ 'title',
+ 'link',
+ 'sort',
+ 'language_id',
+ ];
+
+ public function language(): BelongsTo
+ {
+ return $this->belongsTo(ProjectLanguage::class);
+ }
+}
diff --git a/app/application/app/Policies/ProjectLinkPolicy.php b/app/application/app/Policies/ProjectLinkPolicy.php
new file mode 100644
index 0000000..a8ed52a
--- /dev/null
+++ b/app/application/app/Policies/ProjectLinkPolicy.php
@@ -0,0 +1,34 @@
+hasPermission('project-link.view');
+ }
+
+ public function view(User $user, ProjectLink $link): bool
+ {
+ return $user->hasPermission('project-link.view');
+ }
+
+ public function create(User $user): bool
+ {
+ return $user->hasPermission('project-link.create');
+ }
+
+ public function update(User $user, ProjectLink $link): bool
+ {
+ return $user->hasPermission('project-link.update');
+ }
+
+ public function delete(User $user, ProjectLink $link): bool
+ {
+ return $user->hasPermission('project-link.delete');
+ }
+}
diff --git a/app/application/app/Repositories/ProjectLinkRepository.php b/app/application/app/Repositories/ProjectLinkRepository.php
new file mode 100644
index 0000000..3491e7a
--- /dev/null
+++ b/app/application/app/Repositories/ProjectLinkRepository.php
@@ -0,0 +1,32 @@
+builderCommand->execute(
+ query: ProjectLink::query()->where('project_id', $projectId)->with($with),
+ projectLinkBuilderDto: $projectLinkBuilderDto,
+ );
+
+ return $this->createSearchInstanceCommand->execute($query);
+ }
+
+ public function getLinkById(int $id): ?ProjectLink
+ {
+ return ProjectLink::query()->where('id', $id)->first();
+ }
+}
diff --git a/app/application/app/Services/Admin/Project/LinkService.php b/app/application/app/Services/Admin/Project/LinkService.php
new file mode 100644
index 0000000..e2b6930
--- /dev/null
+++ b/app/application/app/Services/Admin/Project/LinkService.php
@@ -0,0 +1,203 @@
+projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('viewAny', ProjectLink::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ $links = $this->projectLinkRepository->getLinksFromProject(
+ $project->id,
+ $linkBuilderDto,
+ $querySettingsDto->getQueryWith()
+ )->pagination(
+ $querySettingsDto->getLimit(),
+ $querySettingsDto->getPage()
+ );
+
+ return $this->result([
+ 'links' => $links,
+ 'project' => $project,
+ ]);
+ }
+
+ public function create(int $projectId, User $user): ServiceResultError | ServiceResultArray
+ {
+ $project = $this->projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('create', ProjectLink::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ return $this->result([
+ 'link' => new ProjectLink(),
+ 'project' => $project,
+ ]);
+ }
+
+ public function edit(int $projectId, int $linkId, User $user): ServiceResultError | ServiceResultArray
+ {
+ $project = $this->projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ $link = $this->projectLinkRepository->getLinkById($linkId);
+ if (\is_null($link)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('view', $link)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ return $this->result([
+ 'link' => $link,
+ 'project' => $project,
+ ]);
+ }
+
+ public function store(int $projectId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
+ {
+ if ($user->cannot('create', ProjectLink::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ $project = $this->projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if (
+ $data->getLanguageId() !== null
+ && $project->languages()->where('id', $data->getLanguageId())->exists() === false
+ ) {
+ return $this->errValidate(
+ __('validation.exists', ['attribute' => __('validation.attributes.language_id')]),
+ ['language_id' => __('validation.exists', ['attribute' => __('validation.attributes.language_id')])]
+ );
+ }
+
+ try {
+ $link = DB::transaction(function () use ($data, $project, $user) {
+ $dataLink = $this->getDataLink($data);
+ return $this->projectLinkCommandHandler->handleStore($project, $dataLink);
+ });
+ } catch (\Throwable $e) {
+ report($e);
+ return $this->errService(__('Server Error'));
+ }
+
+ return $this->resultStoreUpdateModel($link, __('The link was successfully created'));
+ }
+
+ public function update(int $projectId, int $linkId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
+ {
+ $project = $this->projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if (
+ $data->getLanguageId() !== null
+ && $project->languages()->where('id', $data->getLanguageId())->exists() === false
+ ) {
+ return $this->errValidate(
+ __('validation.exists', ['attribute' => __('validation.attributes.language_id')]),
+ ['language_id' => __('validation.exists', ['attribute' => __('validation.attributes.language_id')])]
+ );
+ }
+
+ $link = $this->projectLinkRepository->getLinkById($linkId);
+ if (\is_null($link)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('update', $link)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ try {
+ $link = DB::transaction(function () use ($data, $link) {
+ $dataLink = $this->getDataLink($data);
+ return $this->projectLinkCommandHandler->handleUpdate($link, $dataLink);
+ });
+ } catch (\Throwable $e) {
+ report($e);
+ return $this->errService(__('Server Error'));
+ }
+
+ return $this->resultStoreUpdateModel($link, __('The link was successfully updated'));
+ }
+
+ public function destroy(int $projectId, int $linkId, User $user): ServiceResultError|ServiceResultSuccess
+ {
+ $project = $this->projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ $link = $this->projectLinkRepository->getLinkById($linkId);
+ if (\is_null($link)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('delete', $link)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ try {
+ DB::transaction(function () use ($link) {
+ $this->projectLinkCommandHandler->handleDestroy($link);
+ });
+ } catch (\Throwable $e) {
+ report($e);
+ return $this->errService(__('Server Error'));
+ }
+
+ return $this->ok(__('The link has been deleted'));
+ }
+
+ private function getDataLink(StoreUpdate $data): array
+ {
+ return [
+ 'title' => $data->getTitle(),
+ 'link' => $data->getLink(),
+ 'sort' => $data->getSort(),
+ 'language_id' => $data->getLanguageId(),
+ ];
+ }
+}
diff --git a/app/application/app/Services/ProjectLink/BuilderCommand.php b/app/application/app/Services/ProjectLink/BuilderCommand.php
new file mode 100644
index 0000000..22c62b9
--- /dev/null
+++ b/app/application/app/Services/ProjectLink/BuilderCommand.php
@@ -0,0 +1,15 @@
+links()->create($data);
+ }
+
+ public function handleUpdate(ProjectLink $link, array $data): ProjectLink
+ {
+ $link->update($data);
+ return $link;
+ }
+
+ public function handleDestroy(ProjectLink $link): void
+ {
+ $link->delete();
+ }
+}
diff --git a/app/application/lang/en.json b/app/application/lang/en.json
index a4fd870..c30cdf8 100644
--- a/app/application/lang/en.json
+++ b/app/application/lang/en.json
@@ -249,5 +249,9 @@
"There was an error adding a language": "There was an error adding a language",
"Select images": "Select images",
"loading": "loading",
- "Project information has been successfully updated": "Project information has been successfully updated"
+ "Project information has been successfully updated": "Project information has been successfully updated",
+ "in all languages": "in all languages",
+ "The link was successfully created": "The link was successfully created",
+ "The link was successfully updated": "The link was successfully updated",
+ "The link has been deleted": "The link has been deleted",
}
diff --git a/app/application/lang/en/admin-sections.php b/app/application/lang/en/admin-sections.php
index 503d7f2..946a4c3 100644
--- a/app/application/lang/en/admin-sections.php
+++ b/app/application/lang/en/admin-sections.php
@@ -10,4 +10,5 @@
'About project' => 'About the project',
'Languages' => 'Languages',
'Last update' => 'Last update',
+ 'Links project' => 'Links from the project',
];
diff --git a/app/application/lang/en/permissions.php b/app/application/lang/en/permissions.php
index 063e379..88b02d2 100644
--- a/app/application/lang/en/permissions.php
+++ b/app/application/lang/en/permissions.php
@@ -3,12 +3,16 @@
return [
'Role' => 'User group',
'User' => 'Users',
- 'Allowed to watch' => 'Allowed to watch',
+
+ 'Allowed to watch' => 'Allowed to watch',
'Allowed to create' => 'Allowed to create',
- 'Allowed to edit' => 'Allowed to edit',
+ 'Allowed to edit' => 'Allowed to edit',
'Allowed to delete' => 'Allowed to delete',
+
'Administrative panel allowed' => 'Administrative panel allowed',
- 'AdminPanel' => 'Administrative panel allowed',
- 'Project' => 'Projects',
+ 'AdminPanel' => 'Administrative panel allowed',
+
+ 'Project' => 'Projects',
'ProjectContent' => 'About the project',
+ 'ProjectLink' => 'Links from the project',
];
diff --git a/app/application/lang/en/validation.php b/app/application/lang/en/validation.php
index c8a8fc9..11fdad1 100644
--- a/app/application/lang/en/validation.php
+++ b/app/application/lang/en/validation.php
@@ -292,5 +292,7 @@
'logo' => 'logo',
'logo.file' => 'logo file',
'logo.delete' => 'remove logo',
+ 'link' => 'link',
+ 'language_id' => 'language',
],
];
diff --git a/app/application/lang/ru.json b/app/application/lang/ru.json
index 0547a0e..9a93a3c 100644
--- a/app/application/lang/ru.json
+++ b/app/application/lang/ru.json
@@ -249,5 +249,9 @@
"There was an error adding a language": "Произошла ошибка при добавлении языка",
"Select images": "Выберите изображения",
"loading": "загрузка",
- "Project information has been successfully updated": "Информация о проекте успешно обновлена"
+ "Project information has been successfully updated": "Информация о проекте успешно обновлена",
+ "in all languages": "на всех языках",
+ "The link was successfully created": "Ссылка успешно создана",
+ "The link was successfully updated": "Ссылка успешно обновлена",
+ "The link has been deleted": "Ссылка удалена"
}
diff --git a/app/application/lang/ru/admin-sections.php b/app/application/lang/ru/admin-sections.php
index 06e553e..5ec768a 100644
--- a/app/application/lang/ru/admin-sections.php
+++ b/app/application/lang/ru/admin-sections.php
@@ -10,4 +10,5 @@
'About project' => 'О проекте',
'Languages' => 'Языки',
'Last update' => 'Последнее обновление',
+ 'Links project' => 'Ссылки от проекта',
];
diff --git a/app/application/lang/ru/permissions.php b/app/application/lang/ru/permissions.php
index fac9c56..dfe8cd7 100644
--- a/app/application/lang/ru/permissions.php
+++ b/app/application/lang/ru/permissions.php
@@ -3,13 +3,16 @@
return [
'Role' => 'Группа пользователей',
'User' => 'Пользователи',
- 'CaptchaToken' => 'Токены для создания капчи',
- 'Allowed to watch' => 'Разрешено смотреть',
+
+ 'Allowed to watch' => 'Разрешено смотреть',
'Allowed to create' => 'Разрешено создать',
- 'Allowed to edit' => 'Разрешено редактировать',
+ 'Allowed to edit' => 'Разрешено редактировать',
'Allowed to delete' => 'Разрешено удалять',
+
'Administrative panel allowed' => 'Административная панель разрешена',
- 'AdminPanel' => 'Административная панель разрешена',
- 'Project' => 'Проекты',
+ 'AdminPanel' => 'Административная панель разрешена',
+
+ 'Project' => 'Проекты',
'ProjectContent' => 'О проекте',
+ 'ProjectLink' => 'Ссылки от проекта',
];
diff --git a/app/application/lang/ru/validation.php b/app/application/lang/ru/validation.php
index 22e30cc..5f48b83 100644
--- a/app/application/lang/ru/validation.php
+++ b/app/application/lang/ru/validation.php
@@ -292,5 +292,7 @@
'logo' => 'логотип',
'logo.file' => 'файл логотипа',
'logo.delete' => 'удалить логотип',
+ 'link' => 'ссылка',
+ 'language_id' => 'язык',
],
];
diff --git a/app/application/resources/views/admin/projects/links/_from.blade.php b/app/application/resources/views/admin/projects/links/_from.blade.php
new file mode 100644
index 0000000..0fd6dd4
--- /dev/null
+++ b/app/application/resources/views/admin/projects/links/_from.blade.php
@@ -0,0 +1,11 @@
+@csrf
+
+
+
+
+
+
+
+@canany(['create', 'update'], $link)
+
+@endcanany
diff --git a/app/application/resources/views/admin/projects/links/_top.blade.php b/app/application/resources/views/admin/projects/links/_top.blade.php
new file mode 100644
index 0000000..d6d356a
--- /dev/null
+++ b/app/application/resources/views/admin/projects/links/_top.blade.php
@@ -0,0 +1,8 @@
+@can('create', \App\Models\ProjectLink::class)
+
+@endcan
diff --git a/app/application/resources/views/admin/projects/links/create.blade.php b/app/application/resources/views/admin/projects/links/create.blade.php
new file mode 100644
index 0000000..196e3f5
--- /dev/null
+++ b/app/application/resources/views/admin/projects/links/create.blade.php
@@ -0,0 +1,17 @@
+@section('meta_title', __('admin-sections.Links project'))
+@section('h1', __('admin-sections.Project') . ': ' . $project->name)
+
+ @include('admin.projects.links._top')
+
+
+
+
+ {{ __('admin-sections.Links project') }}
+
+
+
+
+
+
diff --git a/app/application/resources/views/admin/projects/links/edit.blade.php b/app/application/resources/views/admin/projects/links/edit.blade.php
new file mode 100644
index 0000000..4d2bc86
--- /dev/null
+++ b/app/application/resources/views/admin/projects/links/edit.blade.php
@@ -0,0 +1,18 @@
+@section('meta_title', __('admin-sections.Links project'))
+@section('h1', __('admin-sections.Project') . ': ' . $project->name)
+
+ @include('admin.projects._top')
+
+
+
+
+ {{ __('admin-sections.Links project') }}
+
+
+
+
+
+
diff --git a/app/application/resources/views/admin/projects/links/index.blade.php b/app/application/resources/views/admin/projects/links/index.blade.php
new file mode 100644
index 0000000..fb2419a
--- /dev/null
+++ b/app/application/resources/views/admin/projects/links/index.blade.php
@@ -0,0 +1,61 @@
+@section('meta_title', __('admin-sections.Links project'))
+@section('h1', __('admin-sections.Project') . ': ' . $project->name)
+
+ @include('admin.projects.links._top')
+
+
+ {{ __('admin-sections.Links project') }}
+
+
+
+
+ {{ __('validation.attributes.title') }} |
+ {{ __('validation.attributes.link') }} |
+ {{ __('validation.attributes.language_id') }} |
+ |
+
+
+
+ @foreach($links as $link)
+
+
+
+
+
+
+ {{ $link->title }}
+
+ |
+
+ {{ $link->link }}
+ |
+
+ {{ $link->language?->title ?? __('in all languages') }}
+ |
+
+ @can('delete', $link)
+
+ @endcan
+ |
+
+ @endforeach
+
+
+
+
+
+
+ @push('scripts')
+ @include('admin._scripts._click-confirm', ['alert' => __('Do you want to delete?')])
+ @endpush
+
diff --git a/app/application/resources/views/admin/projects/show.blade.php b/app/application/resources/views/admin/projects/show.blade.php
index 119d2a6..59e8ebc 100644
--- a/app/application/resources/views/admin/projects/show.blade.php
+++ b/app/application/resources/views/admin/projects/show.blade.php
@@ -15,7 +15,7 @@
@can('viewAny', \App\Models\ProjectContent::class)
-
+
@@ -24,6 +24,18 @@
|
@endcan
+ @can('viewAny', \App\Models\ProjectLink::class)
+
+
+
+
+
+
+ {{ __('admin-sections.Links project') }}
+
+ |
+
+ @endcan
diff --git a/app/application/routes/web.php b/app/application/routes/web.php
index 9e6b87d..4ae5654 100644
--- a/app/application/routes/web.php
+++ b/app/application/routes/web.php
@@ -25,6 +25,8 @@
Route::get('about/{language}', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'edit'])->name('about.edit')->where(['language' => '[0-9]+']);
Route::put('about/{language}', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'update'])->name('about.update')->where(['language' => '[0-9]+']);
+ Route::resource('links', \App\Http\Controllers\Admin\Projects\LinksController::class)->except(['show'])->where(['link' => '[0-9]+']);
+
})->where(['project' => '[0-9]+']);
Route::post('languages/new-language', [\App\Http\Controllers\Admin\LanguagesController::class, 'newLanguage'])->name('new-language');
@@ -42,4 +44,3 @@
Route::post('image/upload-resize', [\App\Http\Controllers\Storage\ImagesController::class, 'uploadAndResize'])->name('image_upload_and_resize');
});
});
-
From 24e0cf0eea05c625e466340f00a25cdde250a373 Mon Sep 17 00:00:00 2001
From: Leonid Nikitin
Date: Thu, 18 Apr 2024 19:41:31 +0500
Subject: [PATCH 09/20] The page about the project has been revived.
---
app/application/app/Dto/Builder/Project.php | 9 +-
app/application/app/Enums/CacheTag.php | 16 ++
.../app/Enums/Site/ProjectSection.php | 31 +++
.../app/Http/Controllers/Site/Controller.php | 18 ++
.../Controllers/Site/ProjectsController.php | 62 +++++
.../app/Http/Middleware/AdminPanel.php | 2 +-
.../app/Http/Middleware/Project.php | 50 ++++
.../app/Http/Middleware/ProjectDomain.php | 32 +++
.../Admin/Projects/StoreUpdateRequest.php | 8 +-
.../app/Providers/AppServiceProvider.php | 4 +
.../ProjectLanguageRepository.php | 10 +
.../Repositories/ProjectLinkRepository.php | 10 +
.../app/Repositories/ProjectRepository.php | 5 +
.../Site/PagePossibleWithoutTranslation.php | 37 +++
.../app/Services/Admin/ProjectService.php | 6 +
.../app/Services/ClearCacheCommandHandler.php | 13 +
.../app/Services/Project/BuilderCommand.php | 4 +
app/application/app/Services/Service.php | 9 +
.../app/Services/Site/ProjectService.php | 73 +++++
.../app/View/Components/HomeLayout.php | 14 +
.../View/Components/Site/ChooseLanguage.php | 31 +++
.../app/View/Components/Site/Layout.php | 26 ++
app/application/bootstrap/app.php | 11 +
app/application/lang/en.json | 2 +
app/application/lang/en/site.php | 10 +
app/application/lang/ru.json | 4 +-
app/application/lang/ru/site.php | 10 +
app/application/resources/home/scss/app.scss | 72 +++++
.../resources/home/scss/reset.scss | 73 +++++
.../resources/site/js/_choose-language.js | 8 +
app/application/resources/site/js/_menu.js | 8 +
app/application/resources/site/js/app.js | 2 +
app/application/resources/site/scss/app.scss | 252 ++++++++++++++++++
.../resources/site/scss/reset.scss | 73 +++++
.../components/site/choose-language.blade.php | 26 ++
.../resources/views/layout/home.blade.php | 16 ++
.../resources/views/layout/site.blade.php | 55 ++++
.../site/page-without-translation.blade.php | 5 +
.../views/site/projects/about.blade.php | 6 +
.../views/site/projects/index.blade.php | 18 ++
app/application/routes/web-project.php | 6 +
app/application/vite.config.js | 5 +
42 files changed, 1126 insertions(+), 6 deletions(-)
create mode 100644 app/application/app/Enums/CacheTag.php
create mode 100644 app/application/app/Enums/Site/ProjectSection.php
create mode 100644 app/application/app/Http/Controllers/Site/Controller.php
create mode 100644 app/application/app/Http/Controllers/Site/ProjectsController.php
create mode 100644 app/application/app/Http/Middleware/Project.php
create mode 100644 app/application/app/Http/Middleware/ProjectDomain.php
create mode 100644 app/application/app/ServiceResults/Site/PagePossibleWithoutTranslation.php
create mode 100644 app/application/app/Services/ClearCacheCommandHandler.php
create mode 100644 app/application/app/Services/Site/ProjectService.php
create mode 100644 app/application/app/View/Components/HomeLayout.php
create mode 100644 app/application/app/View/Components/Site/ChooseLanguage.php
create mode 100644 app/application/app/View/Components/Site/Layout.php
create mode 100644 app/application/lang/en/site.php
create mode 100644 app/application/lang/ru/site.php
create mode 100644 app/application/resources/home/scss/app.scss
create mode 100644 app/application/resources/home/scss/reset.scss
create mode 100644 app/application/resources/site/js/_choose-language.js
create mode 100644 app/application/resources/site/js/_menu.js
create mode 100644 app/application/resources/site/js/app.js
create mode 100644 app/application/resources/site/scss/app.scss
create mode 100644 app/application/resources/site/scss/reset.scss
create mode 100644 app/application/resources/views/components/site/choose-language.blade.php
create mode 100644 app/application/resources/views/layout/home.blade.php
create mode 100644 app/application/resources/views/layout/site.blade.php
create mode 100644 app/application/resources/views/site/page-without-translation.blade.php
create mode 100644 app/application/resources/views/site/projects/about.blade.php
create mode 100644 app/application/resources/views/site/projects/index.blade.php
create mode 100644 app/application/routes/web-project.php
diff --git a/app/application/app/Dto/Builder/Project.php b/app/application/app/Dto/Builder/Project.php
index 19127b0..8d777fa 100644
--- a/app/application/app/Dto/Builder/Project.php
+++ b/app/application/app/Dto/Builder/Project.php
@@ -2,9 +2,16 @@
namespace App\Dto\Builder;
+use App\Models\User;
+
final readonly class Project
{
public function __construct(
-
+ private ?bool $isPublic = null,
) { }
+
+ public function isPublic(): ?bool
+ {
+ return $this->isPublic;
+ }
}
diff --git a/app/application/app/Enums/CacheTag.php b/app/application/app/Enums/CacheTag.php
new file mode 100644
index 0000000..1ff3862
--- /dev/null
+++ b/app/application/app/Enums/CacheTag.php
@@ -0,0 +1,16 @@
+value);
+ }
+}
diff --git a/app/application/app/Enums/Site/ProjectSection.php b/app/application/app/Enums/Site/ProjectSection.php
new file mode 100644
index 0000000..e8287db
--- /dev/null
+++ b/app/application/app/Enums/Site/ProjectSection.php
@@ -0,0 +1,31 @@
+http_host === null) {
+ $prefixProject = 'project.';
+ $parameters['project'] = $project->code;
+ }
+
+ $prefixLanguage = '';
+ if ($language?->is_default === false) {
+ $parameters['language'] = $language->code;
+ $prefixLanguage = '-language';
+ }
+
+ return match ($this) {
+ self::Home => \route($prefixProject . 'home' . $prefixLanguage, $parameters),
+ };
+ }
+}
diff --git a/app/application/app/Http/Controllers/Site/Controller.php b/app/application/app/Http/Controllers/Site/Controller.php
new file mode 100644
index 0000000..28edf22
--- /dev/null
+++ b/app/application/app/Http/Controllers/Site/Controller.php
@@ -0,0 +1,18 @@
+ $result->getProject(),
+ 'language' => $result->getLanguage(),
+ ]);
+ }
+}
diff --git a/app/application/app/Http/Controllers/Site/ProjectsController.php b/app/application/app/Http/Controllers/Site/ProjectsController.php
new file mode 100644
index 0000000..3c61c3d
--- /dev/null
+++ b/app/application/app/Http/Controllers/Site/ProjectsController.php
@@ -0,0 +1,62 @@
+user();
+
+ if (\is_null($request->project)) {
+ if (!\is_null($language)) {
+ abort(404);
+ }
+
+ $with = ['storage'];
+ $result = $this->projectService->getProjects($user, $with);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return \view('site.projects.index', $result->getData());
+ }
+
+ $result = $this->projectService->getAboutByProject($request->project, $language, $request->user());
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+ if ($result->isTranslation()) {
+ return $this->viewPageWithoutTranslation($result);
+ }
+
+ return \view('site.projects.about', $result->getData());
+ }
+
+ public function about(string $project, Request $request, ?string $language = null)
+ {
+ $result = $this->projectService->getAbout($project, $language, $request->user());
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+ if ($result->isTranslation()) {
+ return $this->viewPageWithoutTranslation($result);
+ }
+
+ if ($result->getProject()->http_href !== null) {
+ $link = ProjectSection::Home->url($result->getProject(), $result->getLanguage());
+ return \redirect($result->getProject()->http_href . $link, 302);
+ }
+
+ return \view('site.projects.about', $result->getData());
+ }
+}
diff --git a/app/application/app/Http/Middleware/AdminPanel.php b/app/application/app/Http/Middleware/AdminPanel.php
index 4ff3f8b..ec9355f 100644
--- a/app/application/app/Http/Middleware/AdminPanel.php
+++ b/app/application/app/Http/Middleware/AdminPanel.php
@@ -6,7 +6,7 @@
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
-class AdminPanel
+final class AdminPanel
{
/**
* Handle an incoming request.
diff --git a/app/application/app/Http/Middleware/Project.php b/app/application/app/Http/Middleware/Project.php
new file mode 100644
index 0000000..a374785
--- /dev/null
+++ b/app/application/app/Http/Middleware/Project.php
@@ -0,0 +1,50 @@
+route()?->parameter('project');
+ if ($projectCode === null) {
+ abort(404);
+ }
+
+ $seconds = 3600;
+ $project = CacheTag::Project->getCache()->remember(self::class . $projectCode, $seconds, function () use ($projectCode) {
+ return $this->projectRepository->getProjectByCode($projectCode) ?? false;
+ });
+ if ($project === false) {
+ abort(404);
+ }
+
+ unset($request->route()->parameters['project']);
+
+ $routeName = $request->route()->getName();
+ if (
+ $routeName !== null
+ && $project->http_host !== null
+ && $project->http_host !== $request->getSchemeAndHttpHost()
+ ) {
+ $routeName = Str::of($routeName)->replaceStart('project.', '')->toString();
+ $route = \route($routeName, $request->route()->parameters(), false);
+ return redirect($project->http_host . $route, 302);
+ }
+
+ $request->merge(['project' => $project]);
+
+ return $next($request);
+ }
+}
diff --git a/app/application/app/Http/Middleware/ProjectDomain.php b/app/application/app/Http/Middleware/ProjectDomain.php
new file mode 100644
index 0000000..be4a7a5
--- /dev/null
+++ b/app/application/app/Http/Middleware/ProjectDomain.php
@@ -0,0 +1,32 @@
+getSchemeAndHttpHost();
+
+ $seconds = 3600;
+ $project = CacheTag::Project->getCache()->remember(self::class . $httpHost, $seconds, function () use ($httpHost) {
+ return $this->projectRepository->getProjectByHttpHost($httpHost) ?? false;
+ });
+ if ($project === false) {
+ $project = null;
+ }
+ $request->merge(['project' => $project]);
+
+ return $next($request);
+ }
+}
diff --git a/app/application/app/Http/Requests/Admin/Projects/StoreUpdateRequest.php b/app/application/app/Http/Requests/Admin/Projects/StoreUpdateRequest.php
index cf68e57..6af4e5e 100644
--- a/app/application/app/Http/Requests/Admin/Projects/StoreUpdateRequest.php
+++ b/app/application/app/Http/Requests/Admin/Projects/StoreUpdateRequest.php
@@ -18,10 +18,12 @@ class StoreUpdateRequest extends FormRequest implements FormRequestDto
*/
public function rules(): array
{
+ $projectId = $this->project ?? null;
+
return [
'name' => ['required', 'string', 'max:255'],
- 'code' => ['required', 'string', 'min:3', 'max:255', 'regex:/^[a-z0-9_-]+$/i'],
- 'http_host' => ['nullable', 'string', 'max:255', new HttpHost()],
+ 'code' => ['required', 'string', 'min:3', 'max:255', 'regex:/^[a-z0-9_-]+$/'],
+ 'http_host' => ['nullable', 'string', 'max:255', new HttpHost(), 'unique:projects,http_host,' . $projectId],
'is_public' => ['required', 'boolean'],
'logo.file' => ['nullable', 'numeric', 'min:1'],
@@ -29,7 +31,7 @@ public function rules(): array
'languages.items.*.id' => ['nullable', 'numeric'],
'languages.items.*.title' => ['required', 'string', 'max:255'],
- 'languages.items.*.code' => ['required', 'string', 'min:2', 'max:30', 'regex:/^[a-zA-Z_]+$/i'],
+ 'languages.items.*.code' => ['required', 'string', 'min:2', 'max:30', 'regex:/^[a-z_]+$/'],
'languages.items.*.sort' => ['required', 'numeric', 'min:-1000', 'max:1000'],
'languages.default' => ['required', 'numeric', function (string $attribute, mixed $value, \Closure $fail) {
$languages = $this->input('languages.items', []);
diff --git a/app/application/app/Providers/AppServiceProvider.php b/app/application/app/Providers/AppServiceProvider.php
index cdf595d..20abed2 100644
--- a/app/application/app/Providers/AppServiceProvider.php
+++ b/app/application/app/Providers/AppServiceProvider.php
@@ -12,6 +12,7 @@
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
@@ -56,6 +57,9 @@ public function boot(): void
Relation::enforceMorphMap(Morph::map());
+ Route::pattern('language', '[a-z_]+');
+ Route::pattern('project', '[a-z0-9_-]+');
+
$this->configureRateLimiting();
Gate::define('AdminPanel', [\App\Policies\AdminPanel::class, 'view']);
}
diff --git a/app/application/app/Repositories/ProjectLanguageRepository.php b/app/application/app/Repositories/ProjectLanguageRepository.php
index eec7bb7..6b7e4b5 100644
--- a/app/application/app/Repositories/ProjectLanguageRepository.php
+++ b/app/application/app/Repositories/ProjectLanguageRepository.php
@@ -2,10 +2,20 @@
namespace App\Repositories;
+use App\Models\Project;
use App\Models\ProjectLanguage;
final class ProjectLanguageRepository
{
+ public function getProjectLanguageByCodeOrDefault(Project $project, ?string $code): ?ProjectLanguage
+ {
+ if (\is_null($code)) {
+ return $project->languages()->where('is_default', true)->first();
+ }
+
+ return $project->languages()->where('code', $code)->first();
+ }
+
public function isExistsLanguageById(int $projectId, int $languageId): bool
{
return ProjectLanguage::query()
diff --git a/app/application/app/Repositories/ProjectLinkRepository.php b/app/application/app/Repositories/ProjectLinkRepository.php
index 3491e7a..6c29ba4 100644
--- a/app/application/app/Repositories/ProjectLinkRepository.php
+++ b/app/application/app/Repositories/ProjectLinkRepository.php
@@ -3,10 +3,13 @@
namespace App\Repositories;
use App\Contracts\Search;
+use App\Models\Project;
use App\Services\ProjectLink\BuilderCommand;
use App\Services\Search\CreateSearchInstanceCommand;
use App\Dto\Builder\ProjectLink as ProjectLinkBuilderDto;
use App\Models\ProjectLink;
+use Illuminate\Support\Collection;
+use Illuminate\Database\Eloquent\Builder;
final readonly class ProjectLinkRepository
{
@@ -29,4 +32,11 @@ public function getLinkById(int $id): ?ProjectLink
{
return ProjectLink::query()->where('id', $id)->first();
}
+
+ public function getLinksByProject(Project $project, int $languageId): Collection
+ {
+ return $project->links()->where(function (Builder $query) use ($languageId) {
+ $query->whereNull('language_id')->orWhere('language_id', $languageId);
+ })->get();
+ }
}
diff --git a/app/application/app/Repositories/ProjectRepository.php b/app/application/app/Repositories/ProjectRepository.php
index a28e88e..59c424a 100644
--- a/app/application/app/Repositories/ProjectRepository.php
+++ b/app/application/app/Repositories/ProjectRepository.php
@@ -26,6 +26,11 @@ public function getProjects(ProjectBuilderDto $projectBuilderDto, array $with =
return $this->createSearchInstanceCommand->execute($query);
}
+ public function getProjectByHttpHost(string $httpHost): ?Project
+ {
+ return Project::query()->where('http_host', $httpHost)->first();
+ }
+
public function getProjectById(int $id): ?Project
{
return Project::query()->where('id', $id)->first();
diff --git a/app/application/app/ServiceResults/Site/PagePossibleWithoutTranslation.php b/app/application/app/ServiceResults/Site/PagePossibleWithoutTranslation.php
new file mode 100644
index 0000000..af3e03d
--- /dev/null
+++ b/app/application/app/ServiceResults/Site/PagePossibleWithoutTranslation.php
@@ -0,0 +1,37 @@
+isTranslation;
+ }
+
+ public function getData(): array
+ {
+ return $this->data;
+ }
+
+ public function getProject(): Project
+ {
+ return $this->project;
+ }
+
+ public function getLanguage(): ProjectLanguage
+ {
+ return $this->language;
+ }
+}
diff --git a/app/application/app/Services/Admin/ProjectService.php b/app/application/app/Services/Admin/ProjectService.php
index 7b6334c..ccc05dd 100644
--- a/app/application/app/Services/Admin/ProjectService.php
+++ b/app/application/app/Services/Admin/ProjectService.php
@@ -5,6 +5,7 @@
use App\Dto\Builder\Project as ProjectBuilderDto;
use App\Dto\QuerySettingsDto;
use App\Dto\Service\Admin\Project\StoreUpdate;
+use App\Enums\CacheTag;
use App\Enums\Morph;
use App\Models\Project;
use App\Models\ProjectLanguage;
@@ -14,6 +15,7 @@
use App\ServiceResults\ServiceResultError;
use App\ServiceResults\ServiceResultSuccess;
use App\ServiceResults\StoreUpdateResult;
+use App\Services\ClearCacheCommandHandler;
use App\Services\Project\ProjectCommandHandler;
use App\Services\ProjectLanguage\ModelSyncCommand;
use App\Services\Role\CreateAdminRoleForProjectCommand;
@@ -29,6 +31,7 @@ public function __construct(
private readonly CreateAdminRoleForProjectCommand $createAdminRoleForProjectCommand,
private readonly ModelSyncCommand $languageModelSyncCommand,
private readonly StorageService $storageService,
+ private readonly ClearCacheCommandHandler $clearCacheCommandHandler,
) { }
public function index(ProjectBuilderDto $projectBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
@@ -130,6 +133,7 @@ public function store(StoreUpdate $data, User $user): ServiceResultError | Store
return $project;
});
+ $this->clearCacheCommandHandler->byTag(CacheTag::Project);
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
@@ -172,6 +176,7 @@ public function update(int $id, StoreUpdate $data, User $user): ServiceResultErr
return $project;
});
+ $this->clearCacheCommandHandler->byTag(CacheTag::Project);
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
@@ -196,6 +201,7 @@ public function destroy(int $id, User $user): ServiceResultError|ServiceResultSu
DB::transaction(function () use ($project) {
$this->projectCommandHandler->handleDestroy($project);
});
+ $this->clearCacheCommandHandler->byTag(CacheTag::Project);
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
diff --git a/app/application/app/Services/ClearCacheCommandHandler.php b/app/application/app/Services/ClearCacheCommandHandler.php
new file mode 100644
index 0000000..73c9ff9
--- /dev/null
+++ b/app/application/app/Services/ClearCacheCommandHandler.php
@@ -0,0 +1,13 @@
+getCache()->flush();
+ }
+}
diff --git a/app/application/app/Services/Project/BuilderCommand.php b/app/application/app/Services/Project/BuilderCommand.php
index 3bafbea..abf10df 100644
--- a/app/application/app/Services/Project/BuilderCommand.php
+++ b/app/application/app/Services/Project/BuilderCommand.php
@@ -10,6 +10,10 @@
{
public function execute(Relation | Builder $query, ProjectBuilderDto $projectBuilderDto): Relation | Builder
{
+ if ($projectBuilderDto->isPublic() !== null) {
+ $query->where('is_public', $projectBuilderDto->isPublic());
+ }
+
return $query;
}
}
diff --git a/app/application/app/Services/Service.php b/app/application/app/Services/Service.php
index 947ec95..44adf5e 100644
--- a/app/application/app/Services/Service.php
+++ b/app/application/app/Services/Service.php
@@ -2,12 +2,16 @@
namespace App\Services;
+use App\Models\Project;
+use App\Models\ProjectLanguage;
use App\ServiceResults\ServiceResultArray;
use App\ServiceResults\ServiceResultError;
use App\ServiceResults\ServiceResultSuccess;
+use App\ServiceResults\Site\PagePossibleWithoutTranslation;
use App\ServiceResults\StoreUpdateResult;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Response;
+use Illuminate\Support\Facades\Cache;
abstract class Service
{
@@ -56,6 +60,11 @@ final protected function result(array $data = []): ServiceResultArray
return new ServiceResultArray(data: $data);
}
+ final protected function resultSitePage(Project $project, ProjectLanguage $language, array $data, bool $isTranslation): PagePossibleWithoutTranslation
+ {
+ return new PagePossibleWithoutTranslation($project, $language, $data, $isTranslation);
+ }
+
final protected function error(int $code, string $message, array $errors = []): ServiceResultError
{
return new ServiceResultError(
diff --git a/app/application/app/Services/Site/ProjectService.php b/app/application/app/Services/Site/ProjectService.php
new file mode 100644
index 0000000..8573a23
--- /dev/null
+++ b/app/application/app/Services/Site/ProjectService.php
@@ -0,0 +1,73 @@
+cannot('viewAny', Project::class)) {
+ $isPublic = true;
+ }
+ $projectBuilderDto = new ProjectBuilderDto(
+ isPublic: $isPublic,
+ );
+ $projects = $this->projectRepository->getProjects($projectBuilderDto, $with);
+
+ return $this->result([
+ 'projects' => $projects->all(),
+ ]);
+ }
+
+ public function getAbout(string $projectCode, ?string $languageCode, ?User $user = null): ServiceResultError | PagePossibleWithoutTranslation
+ {
+ $project = $this->projectRepository->getProjectByCode($projectCode);
+ if (\is_null($project)) {
+ return $this->errNotFound('Project not found');
+ }
+
+ return $this->getAboutByProject($project, $languageCode, $user);
+ }
+
+ public function getAboutByProject(Project $project, ?string $languageCode, ?User $user = null): ServiceResultError | PagePossibleWithoutTranslation
+ {
+ if (
+ $project->is_public === false
+ && ( $user === null || $user->cannot('view', $project) )
+ ) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+ $language = $this->projectLanguageRepository->getProjectLanguageByCodeOrDefault($project, $languageCode);
+ if (!$language) {
+ return $this->errNotFound(__('Language not found'));
+ }
+
+ $data = [
+ 'project' => $project,
+ 'language' => $language,
+ 'content' => $this->projectContentRepository->getContentByLanguageId($project->id, $language->id),
+ 'links' => $this->projectLinkRepository->getLinksByProject($project, $language->id),
+ ];
+ return $this->resultSitePage($project, $language, $data, \is_null($data['content']));
+ }
+}
diff --git a/app/application/app/View/Components/HomeLayout.php b/app/application/app/View/Components/HomeLayout.php
new file mode 100644
index 0000000..64c1fb4
--- /dev/null
+++ b/app/application/app/View/Components/HomeLayout.php
@@ -0,0 +1,14 @@
+getRequestUri() )->rtrim('/');
+ if ($link->endsWith('/language/' . $this->language->code)) {
+ $link = $link->replace('/language/' . $this->language->code, '', false);
+ }
+
+ return view('components.site.choose-language', [
+ 'language' => $this->language,
+ 'languages' => $this->languages,
+ 'link' => (string) $link,
+ ]);
+ }
+}
diff --git a/app/application/app/View/Components/Site/Layout.php b/app/application/app/View/Components/Site/Layout.php
new file mode 100644
index 0000000..a0f13f5
--- /dev/null
+++ b/app/application/app/View/Components/Site/Layout.php
@@ -0,0 +1,26 @@
+ $this->project,
+ 'logo' => $this->project->getStorageOne(StorageType::Logo),
+ 'language' => $this->language,
+ ]);
+ }
+}
diff --git a/app/application/bootstrap/app.php b/app/application/bootstrap/app.php
index 7b162da..f877115 100644
--- a/app/application/bootstrap/app.php
+++ b/app/application/bootstrap/app.php
@@ -3,12 +3,23 @@
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
+use Illuminate\Support\Facades\Route;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
+ then: function () {
+ Route::middleware([\App\Http\Middleware\ProjectDomain::class])->group(base_path('routes/web-project.php'));
+ Route::middleware([
+ \App\Http\Middleware\ProjectDomain::class,
+ \App\Http\Middleware\Project::class,
+ ])
+ ->prefix('project/{project}')
+ ->as('project.')
+ ->group(base_path('routes/web-project.php'));
+ },
)
->withMiddleware(function (Middleware $middleware) {
//
diff --git a/app/application/lang/en.json b/app/application/lang/en.json
index c30cdf8..1e550c9 100644
--- a/app/application/lang/en.json
+++ b/app/application/lang/en.json
@@ -254,4 +254,6 @@
"The link was successfully created": "The link was successfully created",
"The link was successfully updated": "The link was successfully updated",
"The link has been deleted": "The link has been deleted",
+ "Language not found": "Language not found",
+ "Project not found": "Project not found"
}
diff --git a/app/application/lang/en/site.php b/app/application/lang/en/site.php
new file mode 100644
index 0000000..7dded98
--- /dev/null
+++ b/app/application/lang/en/site.php
@@ -0,0 +1,10 @@
+ "Menu",
+ 'Projects' => 'Projects',
+ 'Project' => 'Project',
+ 'About project' => 'About the project',
+ 'Page without translation' => 'Page without translation',
+ 'Powered by service' => 'Powered by the My Projects website engine',
+ 'Choose language' => 'Choose language',
+];
diff --git a/app/application/lang/ru.json b/app/application/lang/ru.json
index 9a93a3c..c513d72 100644
--- a/app/application/lang/ru.json
+++ b/app/application/lang/ru.json
@@ -253,5 +253,7 @@
"in all languages": "на всех языках",
"The link was successfully created": "Ссылка успешно создана",
"The link was successfully updated": "Ссылка успешно обновлена",
- "The link has been deleted": "Ссылка удалена"
+ "The link has been deleted": "Ссылка удалена",
+ "Language not found": "Язык не найден",
+ "Project not found": "Проект не найден"
}
diff --git a/app/application/lang/ru/site.php b/app/application/lang/ru/site.php
new file mode 100644
index 0000000..380df94
--- /dev/null
+++ b/app/application/lang/ru/site.php
@@ -0,0 +1,10 @@
+ "Меню",
+ 'Projects' => 'Проекты',
+ 'Project' => 'Проект',
+ 'About project' => 'О проекте',
+ 'Page without translation' => 'Страница без перевода',
+ 'Powered by service' => 'Работает на движке сайта «Мои проекты»',
+ 'Choose language' => 'Выберите язык',
+];
diff --git a/app/application/resources/home/scss/app.scss b/app/application/resources/home/scss/app.scss
new file mode 100644
index 0000000..288c278
--- /dev/null
+++ b/app/application/resources/home/scss/app.scss
@@ -0,0 +1,72 @@
+@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
+@import "reset";
+
+body {
+ margin: 0 20px;
+ font-family: "Roboto", sans-serif;
+}
+
+.projects-list {
+ padding: 0;
+ list-style: none;
+ a {
+ display: block;
+ text-decoration: none;
+ }
+ a:hover .projects-list__title {
+ box-shadow: 0px -1px 4px rgba(0, 0, 0, 0.7);
+ }
+ li {
+ padding-bottom: 20px;
+ }
+}
+.projects-list__img {
+ display: block;
+ background: #ddd;
+ height: 150px;
+ img {
+ object-fit: contain;
+ max-width: 100%;
+ max-height: 100%;
+ margin: auto;
+ }
+}
+.projects-list__title {
+ background: #ccc;
+ font-size: 18px;
+ font-weight: bold;
+ display: block;
+ padding: 10px;
+ box-shadow: 0px -1px 4px rgba(0, 0, 0, 0.3);
+ transition: box-shadow 0.3s ease-out;
+}
+
+@media (min-width: 600px) {
+ .projects-list {
+ display: grid;
+ margin-left: -20px;
+ li {
+ margin-left: 20px;
+ }
+ }
+}
+
+@media (min-width: 600px) and (max-width: 899px) {
+ .projects-list {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+@media (min-width: 900px) and (max-width: 1100px) {
+ .projects-list {
+ grid-template-columns: 1fr 1fr 1fr;
+ }
+}
+@media (min-width: 1001px) {
+ body {
+ max-width: 1200px;
+ margin: 0 auto;
+ }
+ .projects-list {
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ }
+}
diff --git a/app/application/resources/home/scss/reset.scss b/app/application/resources/home/scss/reset.scss
new file mode 100644
index 0000000..263554a
--- /dev/null
+++ b/app/application/resources/home/scss/reset.scss
@@ -0,0 +1,73 @@
+/* Box sizing rules */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+/* Prevent font size inflation */
+html {
+ -moz-text-size-adjust: none;
+ -webkit-text-size-adjust: none;
+ text-size-adjust: none;
+}
+
+/* Remove default margin in favour of better control in authored CSS */
+body, h1, h2, h3, h4, p,
+figure, blockquote, dl, dd {
+ margin-block-end: 0;
+}
+
+/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
+ul[role='list'],
+ol[role='list'] {
+ list-style: none;
+}
+
+/* Set core body defaults */
+body {
+ min-height: 100vh;
+ line-height: 1.5;
+}
+
+/* Set shorter line heights on headings and interactive elements */
+h1, h2, h3, h4,
+button, input, label {
+ line-height: 1.1;
+}
+
+/* Balance text wrapping on headings */
+h1, h2,
+h3, h4 {
+ text-wrap: balance;
+}
+
+/* A elements that don't have a class get default styles */
+a:not([class]) {
+ text-decoration-skip-ink: auto;
+ color: currentColor;
+}
+
+/* Make images easier to work with */
+img,
+picture {
+ max-width: 100%;
+ display: block;
+}
+
+/* Inherit fonts for inputs and buttons */
+input, button,
+textarea, select {
+ font-family: inherit;
+ font-size: inherit;
+}
+
+/* Make sure textareas without a rows attribute are not tiny */
+textarea:not([rows]) {
+ min-height: 10em;
+}
+
+/* Anything that has been anchored to should have extra scroll margin */
+:target {
+ scroll-margin-block: 5ex;
+}
diff --git a/app/application/resources/site/js/_choose-language.js b/app/application/resources/site/js/_choose-language.js
new file mode 100644
index 0000000..3c62e18
--- /dev/null
+++ b/app/application/resources/site/js/_choose-language.js
@@ -0,0 +1,8 @@
+let blockLanguage = document.querySelector('#language');
+blockLanguage.querySelector('.language__button').addEventListener('click', (e) => {
+ if (blockLanguage.classList.contains('active')) {
+ blockLanguage.classList.remove('active');
+ } else {
+ blockLanguage.classList.add('active');
+ }
+});
diff --git a/app/application/resources/site/js/_menu.js b/app/application/resources/site/js/_menu.js
new file mode 100644
index 0000000..8ef4d10
--- /dev/null
+++ b/app/application/resources/site/js/_menu.js
@@ -0,0 +1,8 @@
+let body = document.querySelector('body');
+document.querySelector('#mobile-menu').addEventListener('click', (e) => {
+ if (body.classList.contains('mobile-menu-open')) {
+ body.classList.remove('mobile-menu-open');
+ } else {
+ body.classList.add('mobile-menu-open');
+ }
+});
diff --git a/app/application/resources/site/js/app.js b/app/application/resources/site/js/app.js
new file mode 100644
index 0000000..ade6ee6
--- /dev/null
+++ b/app/application/resources/site/js/app.js
@@ -0,0 +1,2 @@
+import './_menu.js';
+import './_choose-language.js';
diff --git a/app/application/resources/site/scss/app.scss b/app/application/resources/site/scss/app.scss
new file mode 100644
index 0000000..e032aa3
--- /dev/null
+++ b/app/application/resources/site/scss/app.scss
@@ -0,0 +1,252 @@
+@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;1,400;1,700&display=swap');
+@import "reset";
+
+html,
+body {
+ height: 100%;
+}
+
+body {
+ font-family: "Roboto", sans-serif;
+ margin: 0;
+}
+
+.wrapper {
+ display: flex;
+ flex-direction: column;
+ min-height: 100%;
+
+ .main-container {
+ flex: 1 0 auto;
+ }
+
+ .section-container {
+ display: flex;
+ flex: 1 0 auto;
+ min-height: 100%;
+ flex-direction: column;
+
+ .content {
+ flex: 1 0 auto;
+ }
+
+ .footer {
+ flex: 0 0 auto;
+ }
+ }
+}
+
+.header {
+ display: grid;
+ grid-template-rows: 1fr;
+ grid-template-columns: 1fr 1fr;
+ grid-template-areas:
+ "logo menu"
+ "language language";
+ padding: 8px;
+ box-shadow: 0 3px 9px rgba(0,0,0,0.48);
+ background: #eee;
+}
+.header_logo {
+ grid-area: logo;
+ max-height: 50px;
+
+ a {
+ text-decoration: none;
+ }
+
+ img {
+ max-height: 100%;
+ }
+
+ strong {
+ color: #0a0e17;
+ font-size: 20px;
+ display: inline-block;
+ vertical-align: middle;
+ padding: 5px 10px;
+ border: 1px solid #0a0e17;
+ border-radius: 4px;
+ }
+}
+#mobile-menu {
+ grid-area: menu;
+ border: 0;
+ background: none;
+ color: #FF2D20;
+ margin-left: auto;
+ cursor: pointer;
+ position: relative;
+ z-index: 10;
+
+ .open {
+ display: block;
+ }
+
+ .close {
+ display: none;
+ margin-top: -5px;
+ }
+}
+#menu {
+ display: none;
+
+ ul {
+ padding: 10px 0 0 20px;
+ margin: 0;
+ list-style: none;
+ }
+
+ a {
+ display: block;
+ text-decoration: none;
+ font-size: 16px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ color: #0a0e17;
+ }
+
+ a.active {
+ font-weight: bold;
+ }
+}
+.menu__title {
+ background: #ccc;
+ font-size: 24px;
+ font-weight: bold;
+ padding: 12px 0 12px 20px;
+ box-shadow: 0 3px 9px rgba(0,0,0,0.48);
+}
+body.mobile-menu-open {
+ #mobile-menu {
+ .open {
+ display: none;
+ }
+
+ .close {
+ display: block;
+ }
+ }
+ #menu {
+ background: #ddd;
+ display: block;
+ position: fixed;
+ left: 0;
+ top: 0;
+ overflow: auto;
+ width: 100%;
+ height: 100%;
+ }
+}
+#language {
+ grid-area: language;
+ margin-top: 10px;
+}
+.language__block {
+ position: relative;
+}
+.language__button {
+ border: 0;
+ box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.48);
+ background: #ccc;
+ width: 100%;
+ cursor: pointer;
+ font-size: 18px;
+ position: relative;
+ padding: 10px;
+ border-radius: 5px;
+ color: #000;
+}
+.language__button__str {
+ position: absolute;
+ top: calc(50% - 8px);
+ right: 10px;
+}
+.language__list {
+ background: #ddd;
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ width: 100%;
+ list-style: none;
+ box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.48);
+ padding: 10px 0;
+ margin: 0;
+ border-radius: 0 0 5px 5px;
+
+ a {
+ text-decoration: none;
+ color: #000;
+ font-size: 16px;
+ display: block;
+ padding: 10px 20px;
+ }
+}
+#language.active {
+ .language__list {
+ display: block;
+ }
+ .language__button {
+ border-radius: 5px 5px 0 0;
+ }
+}
+.main-container {
+ display: flex;
+ flex-direction: column;
+}
+.content {
+ padding-left: 20px;
+ padding-right: 20px;
+ margin-bottom: 20px;
+
+ h1 {
+ font-size: 24px;
+ font-weight: bold;
+ padding-bottom: 10px;
+ margin-bottom: 10px;
+ border-bottom: 1px solid #eee;
+ }
+}
+
+.footer {
+ background-color: #eee;
+ padding: 15px 0 15px 15px;
+ box-shadow: 5px 0px 5px rgba(0,0,0,0.48);
+
+ a {
+ text-decoration: none;
+ color: #0a0e17;
+ font-size: 18px;
+ font-weight: bold;
+ }
+}
+
+@media (min-width: 1000px) {
+ body {
+ max-width: 1400px;
+ margin: 0 auto;
+ }
+ .header {
+ grid-template-areas: "logo language";
+ grid-template-columns: 1fr 200px;
+ }
+ #mobile-menu {
+ display: none;
+ }
+ .main-container {
+ flex-direction: row;
+ }
+ .language__button {
+ padding: 7px 10px 7px 0;
+ }
+ #menu {
+ background: #eee;
+ display: block;
+ width: 175px;
+ box-shadow: 0px 7px 8px rgba(0,0,0,0.48);
+ }
+ .menu__title {
+ display: none;
+ }
+}
diff --git a/app/application/resources/site/scss/reset.scss b/app/application/resources/site/scss/reset.scss
new file mode 100644
index 0000000..263554a
--- /dev/null
+++ b/app/application/resources/site/scss/reset.scss
@@ -0,0 +1,73 @@
+/* Box sizing rules */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+/* Prevent font size inflation */
+html {
+ -moz-text-size-adjust: none;
+ -webkit-text-size-adjust: none;
+ text-size-adjust: none;
+}
+
+/* Remove default margin in favour of better control in authored CSS */
+body, h1, h2, h3, h4, p,
+figure, blockquote, dl, dd {
+ margin-block-end: 0;
+}
+
+/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
+ul[role='list'],
+ol[role='list'] {
+ list-style: none;
+}
+
+/* Set core body defaults */
+body {
+ min-height: 100vh;
+ line-height: 1.5;
+}
+
+/* Set shorter line heights on headings and interactive elements */
+h1, h2, h3, h4,
+button, input, label {
+ line-height: 1.1;
+}
+
+/* Balance text wrapping on headings */
+h1, h2,
+h3, h4 {
+ text-wrap: balance;
+}
+
+/* A elements that don't have a class get default styles */
+a:not([class]) {
+ text-decoration-skip-ink: auto;
+ color: currentColor;
+}
+
+/* Make images easier to work with */
+img,
+picture {
+ max-width: 100%;
+ display: block;
+}
+
+/* Inherit fonts for inputs and buttons */
+input, button,
+textarea, select {
+ font-family: inherit;
+ font-size: inherit;
+}
+
+/* Make sure textareas without a rows attribute are not tiny */
+textarea:not([rows]) {
+ min-height: 10em;
+}
+
+/* Anything that has been anchored to should have extra scroll margin */
+:target {
+ scroll-margin-block: 5ex;
+}
diff --git a/app/application/resources/views/components/site/choose-language.blade.php b/app/application/resources/views/components/site/choose-language.blade.php
new file mode 100644
index 0000000..9c15d25
--- /dev/null
+++ b/app/application/resources/views/components/site/choose-language.blade.php
@@ -0,0 +1,26 @@
+
+
+
+
+ @foreach($languages as $lang)
+ @php
+ $code = '';
+ if ($lang->is_default !== true) {
+ $code = '/language/' . $lang->code;
+ } else if($link === '') {
+ $code = '/';
+ }
+ @endphp
+ - {{ $lang->title }}
+ @endforeach
+
+
+
diff --git a/app/application/resources/views/layout/home.blade.php b/app/application/resources/views/layout/home.blade.php
new file mode 100644
index 0000000..5e1a6af
--- /dev/null
+++ b/app/application/resources/views/layout/home.blade.php
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ @yield('meta_title', '')
+
+
+
+ @vite('resources/home/scss/app.scss')
+
+
+ {{ $slot }}
+
+
diff --git a/app/application/resources/views/layout/site.blade.php b/app/application/resources/views/layout/site.blade.php
new file mode 100644
index 0000000..3440be9
--- /dev/null
+++ b/app/application/resources/views/layout/site.blade.php
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+ @yield('meta_title', '')
+
+
+
+ @vite('resources/site/scss/app.scss')
+
+
+
+
+
+
+
+
+ @yield('h1', '')
+ {{ $slot }}
+
+
+
+
+
+ @vite('resources/site/js/app.js')
+
+
diff --git a/app/application/resources/views/site/page-without-translation.blade.php b/app/application/resources/views/site/page-without-translation.blade.php
new file mode 100644
index 0000000..a405f3b
--- /dev/null
+++ b/app/application/resources/views/site/page-without-translation.blade.php
@@ -0,0 +1,5 @@
+@section('h1', __('site.Page without translation'))
+
+
+
+
diff --git a/app/application/resources/views/site/projects/about.blade.php b/app/application/resources/views/site/projects/about.blade.php
new file mode 100644
index 0000000..ff8225f
--- /dev/null
+++ b/app/application/resources/views/site/projects/about.blade.php
@@ -0,0 +1,6 @@
+@section('meta_title', __('site.Project') . ': ' . $content->title)
+@section('h1', $content->title)
+
+
+ {!! $content->description !!}
+
diff --git a/app/application/resources/views/site/projects/index.blade.php b/app/application/resources/views/site/projects/index.blade.php
new file mode 100644
index 0000000..386f2ba
--- /dev/null
+++ b/app/application/resources/views/site/projects/index.blade.php
@@ -0,0 +1,18 @@
+@section('meta_title', __('site.Projects'))
+
+
+ {{ __('site.Projects') }}
+
+
diff --git a/app/application/routes/web-project.php b/app/application/routes/web-project.php
new file mode 100644
index 0000000..5692160
--- /dev/null
+++ b/app/application/routes/web-project.php
@@ -0,0 +1,6 @@
+name('home');
+Route::get('/language/{language}', [\App\Http\Controllers\Site\ProjectsController::class, 'index'])->name('home-language');
diff --git a/app/application/vite.config.js b/app/application/vite.config.js
index dd794b1..04dcacf 100644
--- a/app/application/vite.config.js
+++ b/app/application/vite.config.js
@@ -8,6 +8,11 @@ export default defineConfig({
input: [
'resources/volt/scss/app.scss',
'resources/volt/js/app.js',
+
+ 'resources/home/scss/app.scss',
+
+ 'resources/site/scss/app.scss',
+ 'resources/site/js/app.js',
],
refresh: true,
}),
From 05fabd7650f26dd55cbd3ce554799693c981cb93 Mon Sep 17 00:00:00 2001
From: Leonid Nikitin
Date: Thu, 18 Apr 2024 21:46:22 +0500
Subject: [PATCH 10/20] Refactoring.
Removed unnecessary methods.
---
.../Controllers/Site/ProjectsController.php | 18 ------------------
.../app/Services/Site/ProjectService.php | 10 ----------
2 files changed, 28 deletions(-)
diff --git a/app/application/app/Http/Controllers/Site/ProjectsController.php b/app/application/app/Http/Controllers/Site/ProjectsController.php
index 3c61c3d..8febecd 100644
--- a/app/application/app/Http/Controllers/Site/ProjectsController.php
+++ b/app/application/app/Http/Controllers/Site/ProjectsController.php
@@ -41,22 +41,4 @@ public function index(Request $request, ?string $language = null): View
return \view('site.projects.about', $result->getData());
}
-
- public function about(string $project, Request $request, ?string $language = null)
- {
- $result = $this->projectService->getAbout($project, $language, $request->user());
- if ($result->isError()) {
- $this->errors($result);
- }
- if ($result->isTranslation()) {
- return $this->viewPageWithoutTranslation($result);
- }
-
- if ($result->getProject()->http_href !== null) {
- $link = ProjectSection::Home->url($result->getProject(), $result->getLanguage());
- return \redirect($result->getProject()->http_href . $link, 302);
- }
-
- return \view('site.projects.about', $result->getData());
- }
}
diff --git a/app/application/app/Services/Site/ProjectService.php b/app/application/app/Services/Site/ProjectService.php
index 8573a23..8cfb9c1 100644
--- a/app/application/app/Services/Site/ProjectService.php
+++ b/app/application/app/Services/Site/ProjectService.php
@@ -39,16 +39,6 @@ public function getProjects(?User $user, array $with): ServiceResultError | Serv
]);
}
- public function getAbout(string $projectCode, ?string $languageCode, ?User $user = null): ServiceResultError | PagePossibleWithoutTranslation
- {
- $project = $this->projectRepository->getProjectByCode($projectCode);
- if (\is_null($project)) {
- return $this->errNotFound('Project not found');
- }
-
- return $this->getAboutByProject($project, $languageCode, $user);
- }
-
public function getAboutByProject(Project $project, ?string $languageCode, ?User $user = null): ServiceResultError | PagePossibleWithoutTranslation
{
if (
From 435e793eb2ac96fb084a3838c70c762e6113aadc Mon Sep 17 00:00:00 2001
From: Leonid Nikitin
Date: Thu, 18 Apr 2024 21:46:58 +0500
Subject: [PATCH 11/20] Added target="_blank".
---
app/application/resources/views/site/projects/index.blade.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/application/resources/views/site/projects/index.blade.php b/app/application/resources/views/site/projects/index.blade.php
index 386f2ba..636b25c 100644
--- a/app/application/resources/views/site/projects/index.blade.php
+++ b/app/application/resources/views/site/projects/index.blade.php
@@ -8,7 +8,7 @@
$logo = $project->getStorageOne(\App\Enums\StorageType::Logo);
@endphp
-
+
@if($logo)@endif
{{ $project->name }}
From a648ba3db9af7539aecbe14a5ee063bfbd40eae3 Mon Sep 17 00:00:00 2001
From: Leonid Nikitin
Date: Thu, 18 Apr 2024 21:47:47 +0500
Subject: [PATCH 12/20] Correcting the link to the project with your domain.
---
app/application/app/Enums/Site/ProjectSection.php | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/app/application/app/Enums/Site/ProjectSection.php b/app/application/app/Enums/Site/ProjectSection.php
index e8287db..5ab5228 100644
--- a/app/application/app/Enums/Site/ProjectSection.php
+++ b/app/application/app/Enums/Site/ProjectSection.php
@@ -24,8 +24,10 @@ public function url(Project $project, ?ProjectLanguage $language = null): string
$prefixLanguage = '-language';
}
- return match ($this) {
- self::Home => \route($prefixProject . 'home' . $prefixLanguage, $parameters),
+ $route = match ($this) {
+ self::Home => \route($prefixProject . 'home' . $prefixLanguage, $parameters, false),
};
+
+ return $project->http_host . $route;
}
}
From 491249c8d8b2b6069fac2f042f8869c66f3ee691 Mon Sep 17 00:00:00 2001
From: Leonid Nikitin
Date: Mon, 22 Apr 2024 23:52:04 +0500
Subject: [PATCH 13/20] Added the ability to dynamically translate on the
project website.
---
.../Dto/Service/Admin/Project/Language.php | 23 ++++-
.../Admin/Project/Translation/Translation.php | 23 +++++
.../Project/Translation/Translations.php | 35 +++++++
.../Admin/Project/Translation/Update.php | 17 ++++
app/application/app/Enums/CacheTag.php | 1 +
app/application/app/Enums/Permission.php | 5 +
.../Transaction/TranslationsException.php | 8 ++
.../Admin/Projects/TranslationsController.php | 54 +++++++++++
.../app/Http/Controllers/Site/Controller.php | 2 +-
.../Controllers/Site/ProjectsController.php | 15 ++-
.../{Project.php => ProjectAndLanguage.php} | 40 ++++----
...omain.php => ProjectDomainAndLanguage.php} | 21 +++--
.../app/Http/Middleware/ProjectLanguage.php | 43 +++++++++
.../Admin/Projects/StoreUpdateRequest.php | 10 ++
.../Projects/Translations/UpdateRequest.php | 38 ++++++++
.../app/Models/ProjectLanguage.php | 27 +++++-
.../app/Models/ProjectTranslation.php | 26 ++++++
.../app/Policies/ProjectTranslationPolicy.php | 24 +++++
.../ProjectTranslationRepository.php | 25 +++++
.../Site/PagePossibleWithoutTranslation.php | 8 +-
.../Admin/Project/TranslationService.php | 91 +++++++++++++++++++
.../ProjectLanguage/ModelSyncCommand.php | 2 +
.../ProjectTranslation/ModelSyncCommand.php | 60 ++++++++++++
app/application/app/Services/Service.php | 6 +-
.../app/Services/Site/ProjectService.php | 17 ++--
.../app/Services/WebsiteTranslations.php | 23 +++++
.../View/Components/Site/ChooseLanguage.php | 24 +++--
.../app/View/Components/Site/Layout.php | 6 +-
.../View/Components/Volt/Forms/Languages.php | 16 +++-
app/application/bootstrap/app.php | 7 +-
...em_language_to_project_languages_table.php | 30 ++++++
..._19_084614_create_project_translations.php | 36 ++++++++
app/application/lang/en.json | 3 +-
app/application/lang/en/admin-sections.php | 1 +
app/application/lang/en/permissions.php | 1 +
app/application/lang/en/validation.php | 7 ++
app/application/lang/ru.json | 3 +-
app/application/lang/ru/admin-sections.php | 1 +
app/application/lang/ru/permissions.php | 1 +
app/application/lang/ru/validation.php | 7 ++
app/application/resources/site/scss/app.scss | 15 ++-
.../views/admin/projects/links/edit.blade.php | 2 +-
.../views/admin/projects/show.blade.php | 12 +++
.../projects/translations/edit.blade.php | 42 +++++++++
.../projects/translations/languages.blade.php | 36 ++++++++
.../components/site/choose-language.blade.php | 4 +-
.../components/volt/forms/input.blade.php | 12 ++-
.../components/volt/forms/languages.blade.php | 6 +-
.../volt/forms/languages/input.blade.php | 2 +-
.../volt/forms/languages/language.blade.php | 35 +++++--
.../forms/languages/system_lang.blade.php | 9 ++
.../resources/views/layout/site.blade.php | 12 +--
.../site/page-without-translation.blade.php | 4 +-
.../views/site/projects/about.blade.php | 4 +-
app/application/routes/web.php | 4 +
55 files changed, 867 insertions(+), 119 deletions(-)
create mode 100644 app/application/app/Dto/Service/Admin/Project/Translation/Translation.php
create mode 100644 app/application/app/Dto/Service/Admin/Project/Translation/Translations.php
create mode 100644 app/application/app/Dto/Service/Admin/Project/Translation/Update.php
create mode 100644 app/application/app/Exceptions/Dto/Admin/Project/Transaction/TranslationsException.php
create mode 100644 app/application/app/Http/Controllers/Admin/Projects/TranslationsController.php
rename app/application/app/Http/Middleware/{Project.php => ProjectAndLanguage.php} (56%)
rename app/application/app/Http/Middleware/{ProjectDomain.php => ProjectDomainAndLanguage.php} (52%)
create mode 100644 app/application/app/Http/Middleware/ProjectLanguage.php
create mode 100644 app/application/app/Http/Requests/Admin/Projects/Translations/UpdateRequest.php
create mode 100644 app/application/app/Models/ProjectTranslation.php
create mode 100644 app/application/app/Policies/ProjectTranslationPolicy.php
create mode 100644 app/application/app/Repositories/ProjectTranslationRepository.php
create mode 100644 app/application/app/Services/Admin/Project/TranslationService.php
create mode 100644 app/application/app/Services/ProjectTranslation/ModelSyncCommand.php
create mode 100644 app/application/app/Services/WebsiteTranslations.php
create mode 100644 app/application/database/migrations/2024_04_18_144547_add_system_language_to_project_languages_table.php
create mode 100644 app/application/database/migrations/2024_04_19_084614_create_project_translations.php
create mode 100644 app/application/resources/views/admin/projects/translations/edit.blade.php
create mode 100644 app/application/resources/views/admin/projects/translations/languages.blade.php
create mode 100644 app/application/resources/views/components/volt/forms/languages/system_lang.blade.php
diff --git a/app/application/app/Dto/Service/Admin/Project/Language.php b/app/application/app/Dto/Service/Admin/Project/Language.php
index c8fb97d..468db45 100644
--- a/app/application/app/Dto/Service/Admin/Project/Language.php
+++ b/app/application/app/Dto/Service/Admin/Project/Language.php
@@ -3,15 +3,18 @@
namespace App\Dto\Service\Admin\Project;
use App\Dto\Service\Dto;
+use App\Enums\Lang;
final readonly class Language extends Dto
{
public function __construct(
- private string $title,
- private string $code,
- private int $sort,
- private bool $isDefault,
- private ?int $id,
+ private string $title,
+ private string $code,
+ private int $sort,
+ private bool $isDefault,
+ private ?string $isoCode = null,
+ private ?Lang $systemLang = null,
+ private ?int $id = null,
) { }
public function getTitle(): string
@@ -38,4 +41,14 @@ public function getId(): ?int
{
return $this->id;
}
+
+ public function getIsoCode(): ?string
+ {
+ return $this->isoCode;
+ }
+
+ public function getSystemLang(): ?Lang
+ {
+ return $this->systemLang;
+ }
}
diff --git a/app/application/app/Dto/Service/Admin/Project/Translation/Translation.php b/app/application/app/Dto/Service/Admin/Project/Translation/Translation.php
new file mode 100644
index 0000000..0dc89fb
--- /dev/null
+++ b/app/application/app/Dto/Service/Admin/Project/Translation/Translation.php
@@ -0,0 +1,23 @@
+code;
+ }
+
+ public function getText(): ?string
+ {
+ return $this->text;
+ }
+}
diff --git a/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php b/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php
new file mode 100644
index 0000000..a0ac031
--- /dev/null
+++ b/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php
@@ -0,0 +1,35 @@
+translations[] = new Translation($code, $text);
+ }
+
+ public function getTranslations(): array
+ {
+ return $this->translations;
+ }
+
+ public static function getTranslationCodes(): array
+ {
+ return [
+ 'site.Menu',
+ 'site.Powered by service',
+ 'site.About project',
+ 'site.Choose language',
+ 'site.Page without translation',
+ 'site.Project',
+ ];
+ }
+}
diff --git a/app/application/app/Dto/Service/Admin/Project/Translation/Update.php b/app/application/app/Dto/Service/Admin/Project/Translation/Update.php
new file mode 100644
index 0000000..3ce9e24
--- /dev/null
+++ b/app/application/app/Dto/Service/Admin/Project/Translation/Update.php
@@ -0,0 +1,17 @@
+translations;
+ }
+}
diff --git a/app/application/app/Enums/CacheTag.php b/app/application/app/Enums/CacheTag.php
index 1ff3862..316900c 100644
--- a/app/application/app/Enums/CacheTag.php
+++ b/app/application/app/Enums/CacheTag.php
@@ -8,6 +8,7 @@
enum CacheTag: string
{
case Project = 'project';
+ case ProjectTranslation = 'project_translation';
public function getCache(): TaggedCache
{
diff --git a/app/application/app/Enums/Permission.php b/app/application/app/Enums/Permission.php
index bdf8d14..652a7d5 100644
--- a/app/application/app/Enums/Permission.php
+++ b/app/application/app/Enums/Permission.php
@@ -10,6 +10,7 @@ enum Permission: string
case Project = 'project';
case ProjectContent = 'project-content';
case ProjectLink = 'project-link';
+ case ProjectTranslation = 'project-translation';
public function getPermissions(): array
{
@@ -22,6 +23,10 @@ public function getPermissions(): array
'create' => __('permissions.Allowed to create'),
'update' => __('permissions.Allowed to edit'),
],
+ self::ProjectTranslation => [
+ 'view' => __('permissions.Allowed to watch'),
+ 'update' => __('permissions.Allowed to edit'),
+ ],
default => $this->getBasePermissions()
};
diff --git a/app/application/app/Exceptions/Dto/Admin/Project/Transaction/TranslationsException.php b/app/application/app/Exceptions/Dto/Admin/Project/Transaction/TranslationsException.php
new file mode 100644
index 0000000..b0b04a7
--- /dev/null
+++ b/app/application/app/Exceptions/Dto/Admin/Project/Transaction/TranslationsException.php
@@ -0,0 +1,8 @@
+user();
+ $result = $this->translationService->languages($projectId, $user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('admin/projects/translations/languages', $result->getData());
+ }
+
+ public function edit(int $projectId, int $languageId, Request $request): View
+ {
+ $user = $request->user();
+ $result = $this->translationService->edit($projectId, $languageId, $user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('admin/projects/translations/edit', $result->getData());
+ }
+
+ public function update(int $projectId, int $languageId, UpdateRequest $request): RedirectResponse
+ {
+ $user = $request->user();
+ $data = $request->getDto();
+ $result = $this->translationService->update($projectId, $languageId, $data, $user);
+ if ($result->isError()) {
+ return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
+ }
+
+ return redirect()->route('admin.projects.translations.edit', [
+ 'project' => $projectId,
+ 'language' => $languageId,
+ ])->withSuccess($result->getMessage());
+ }
+}
diff --git a/app/application/app/Http/Controllers/Site/Controller.php b/app/application/app/Http/Controllers/Site/Controller.php
index 28edf22..1a23afd 100644
--- a/app/application/app/Http/Controllers/Site/Controller.php
+++ b/app/application/app/Http/Controllers/Site/Controller.php
@@ -12,7 +12,7 @@ protected function viewPageWithoutTranslation(PagePossibleWithoutTranslation $re
{
return \view('site.page-without-translation', [
'project' => $result->getProject(),
- 'language' => $result->getLanguage(),
+ 'websiteTranslations' => $result->getWebsiteTranslations(),
]);
}
}
diff --git a/app/application/app/Http/Controllers/Site/ProjectsController.php b/app/application/app/Http/Controllers/Site/ProjectsController.php
index 8febecd..cd227ac 100644
--- a/app/application/app/Http/Controllers/Site/ProjectsController.php
+++ b/app/application/app/Http/Controllers/Site/ProjectsController.php
@@ -2,7 +2,6 @@
namespace App\Http\Controllers\Site;
-use App\Enums\Site\ProjectSection;
use App\Services\Site\ProjectService;
use Illuminate\Http\Request;
use Illuminate\View\View;
@@ -13,15 +12,13 @@ public function __construct(
private readonly ProjectService $projectService,
) { }
- public function index(Request $request, ?string $language = null): View
+ public function index(Request $request): View
{
- $user = $request->user();
-
- if (\is_null($request->project)) {
- if (!\is_null($language)) {
- abort(404);
- }
+ $user = $request->user();
+ $project = $request->get('project');
+ $websiteTranslations = $request->get('websiteTranslations');
+ if (\is_null($project)) {
$with = ['storage'];
$result = $this->projectService->getProjects($user, $with);
if ($result->isError()) {
@@ -31,7 +28,7 @@ public function index(Request $request, ?string $language = null): View
return \view('site.projects.index', $result->getData());
}
- $result = $this->projectService->getAboutByProject($request->project, $language, $request->user());
+ $result = $this->projectService->getAboutByProject($project, $websiteTranslations, $request->user());
if ($result->isError()) {
$this->errors($result);
}
diff --git a/app/application/app/Http/Middleware/Project.php b/app/application/app/Http/Middleware/ProjectAndLanguage.php
similarity index 56%
rename from app/application/app/Http/Middleware/Project.php
rename to app/application/app/Http/Middleware/ProjectAndLanguage.php
index a374785..15215af 100644
--- a/app/application/app/Http/Middleware/Project.php
+++ b/app/application/app/Http/Middleware/ProjectAndLanguage.php
@@ -3,23 +3,17 @@
namespace App\Http\Middleware;
use App\Enums\CacheTag;
-use App\Repositories\ProjectRepository;
use Illuminate\Http\Request;
-use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
use Closure;
-class Project
+class ProjectAndLanguage extends ProjectLanguage
{
- public function __construct(
- private readonly ProjectRepository $projectRepository,
- ) { }
-
public function handle(Request $request, Closure $next): Response
{
$projectCode = $request->route()?->parameter('project');
if ($projectCode === null) {
- abort(404);
+ abort(Response::HTTP_NOT_FOUND);
}
$seconds = 3600;
@@ -27,23 +21,27 @@ public function handle(Request $request, Closure $next): Response
return $this->projectRepository->getProjectByCode($projectCode) ?? false;
});
if ($project === false) {
- abort(404);
+ abort(Response::HTTP_NOT_FOUND);
+ }
+
+ if (
+ $project->http_host !== null
+ && $project->http_host !== $request->getSchemeAndHttpHost()
+ ) {
+ return redirect($project->http_host, 302);
+ }
+
+ $languageCode = $request->route()?->parameter('language');
+ $websiteTranslations = $this->getWebsiteTranslations($project, $languageCode);
+ if (\is_null($websiteTranslations)) {
+ abort(Response::HTTP_NOT_FOUND);
}
unset($request->route()->parameters['project']);
+ unset($request->route()->parameters['language']);
- $routeName = $request->route()->getName();
- if (
- $routeName !== null
- && $project->http_host !== null
- && $project->http_host !== $request->getSchemeAndHttpHost()
- ) {
- $routeName = Str::of($routeName)->replaceStart('project.', '')->toString();
- $route = \route($routeName, $request->route()->parameters(), false);
- return redirect($project->http_host . $route, 302);
- }
-
- $request->merge(['project' => $project]);
+ $request->attributes->set('project', $project);
+ $request->attributes->set('websiteTranslations', $websiteTranslations);
return $next($request);
}
diff --git a/app/application/app/Http/Middleware/ProjectDomain.php b/app/application/app/Http/Middleware/ProjectDomainAndLanguage.php
similarity index 52%
rename from app/application/app/Http/Middleware/ProjectDomain.php
rename to app/application/app/Http/Middleware/ProjectDomainAndLanguage.php
index be4a7a5..6503c95 100644
--- a/app/application/app/Http/Middleware/ProjectDomain.php
+++ b/app/application/app/Http/Middleware/ProjectDomainAndLanguage.php
@@ -3,17 +3,12 @@
namespace App\Http\Middleware;
use App\Enums\CacheTag;
-use App\Repositories\ProjectRepository;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
-final class ProjectDomain
+final class ProjectDomainAndLanguage extends ProjectLanguage
{
- public function __construct(
- private readonly ProjectRepository $projectRepository,
- ) { }
-
public function handle(Request $request, Closure $next): Response
{
$httpHost = $request->getSchemeAndHttpHost();
@@ -25,7 +20,19 @@ public function handle(Request $request, Closure $next): Response
if ($project === false) {
$project = null;
}
- $request->merge(['project' => $project]);
+
+ $websiteTranslations = null;
+ if ($project !== null) {
+ $languageCode = $request->route()?->parameter('language');
+ $websiteTranslations = $this->getWebsiteTranslations($project, $languageCode);
+ if (\is_null($websiteTranslations)) {
+ abort(Response::HTTP_NOT_FOUND);
+ }
+ }
+
+ unset($request->route()->parameters['language']);
+ $request->attributes->set('project', $project);
+ $request->attributes->set('websiteTranslations', $websiteTranslations);
return $next($request);
}
diff --git a/app/application/app/Http/Middleware/ProjectLanguage.php b/app/application/app/Http/Middleware/ProjectLanguage.php
new file mode 100644
index 0000000..efaea63
--- /dev/null
+++ b/app/application/app/Http/Middleware/ProjectLanguage.php
@@ -0,0 +1,43 @@
+getCache()->remember(self::class . $project->id . '-' . $languageCode, $seconds, function () use ($project, $languageCode) {
+ return $this->projectLanguageRepository->getProjectLanguageByCodeOrDefault($project, $languageCode) ?? false;
+ });
+ if ($language === false) {
+ return null;
+ }
+ if ($language !== null) {
+ if ($language->system_lang) {
+ App::setLocale($language->system_lang->getLocale());
+ }
+ }
+
+ $seconds = 3600 * 24;
+ $translations = CacheTag::ProjectTranslation->getCache()->remember(self::class . '-translations-' . $project->id . '-' . $language->id, $seconds, function () use ($project, $language) {
+ return $this->projectTranslationRepository->getProjectTranslations($project->id, $language->id)->all()->pluck('text', 'code')->toArray();
+ });
+
+ return new WebsiteTranslations($language, $translations);
+ }
+}
diff --git a/app/application/app/Http/Requests/Admin/Projects/StoreUpdateRequest.php b/app/application/app/Http/Requests/Admin/Projects/StoreUpdateRequest.php
index 6af4e5e..9917b3f 100644
--- a/app/application/app/Http/Requests/Admin/Projects/StoreUpdateRequest.php
+++ b/app/application/app/Http/Requests/Admin/Projects/StoreUpdateRequest.php
@@ -7,9 +7,11 @@
use App\Dto\Service\Admin\Project\Languages;
use App\Dto\Service\Admin\Project\StoreUpdate;
use App\Dto\Service\Storage\Storages;
+use App\Enums\Lang;
use App\Enums\StorageType;
use App\Rules\HttpHost;
use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rules\Enum;
class StoreUpdateRequest extends FormRequest implements FormRequestDto
{
@@ -33,6 +35,8 @@ public function rules(): array
'languages.items.*.title' => ['required', 'string', 'max:255'],
'languages.items.*.code' => ['required', 'string', 'min:2', 'max:30', 'regex:/^[a-z_]+$/'],
'languages.items.*.sort' => ['required', 'numeric', 'min:-1000', 'max:1000'],
+ 'languages.items.*.system_lang' => ['nullable', new Enum(Lang::class)],
+ 'languages.items.*.iso_code' => ['nullable', 'string', 'max:30', 'regex:/^[a-zA-Z-]+$/'],
'languages.default' => ['required', 'numeric', function (string $attribute, mixed $value, \Closure $fail) {
$languages = $this->input('languages.items', []);
if (!isset($languages[$value])) {
@@ -81,11 +85,17 @@ private function languages(): Languages
if ($languageId !== null) {
$languageId = (int) $languageId;
}
+ $systemLang = null;
+ if ($lang['system_lang'] !== null) {
+ $systemLang = Lang::tryFrom((int) $lang['system_lang']);
+ }
$language = new Language(
title: $lang['title'],
code: $lang['code'],
sort: (int) $lang['sort'],
isDefault: ($default === $index),
+ isoCode: $lang['iso_code'] ?? null,
+ systemLang: $systemLang,
id: $languageId,
);
$languages->addLanguage($language);
diff --git a/app/application/app/Http/Requests/Admin/Projects/Translations/UpdateRequest.php b/app/application/app/Http/Requests/Admin/Projects/Translations/UpdateRequest.php
new file mode 100644
index 0000000..f185a2d
--- /dev/null
+++ b/app/application/app/Http/Requests/Admin/Projects/Translations/UpdateRequest.php
@@ -0,0 +1,38 @@
+ ['nullable', 'array'],
+ 'translations.*.code' => ['required', 'string', new In(Translations::getTranslationCodes())],
+ 'translations.*.text' => ['nullable', 'string', 'max:1000'],
+ ];
+ }
+
+ public function getDto(): Update
+ {
+ $translations = new Translations();
+ foreach ($this->input('translations', []) as $translation) {
+ $translations->addTranslation(
+ code: $translation['code'],
+ text: $translation['text'] ?? null,
+ );
+ }
+
+ return new Update($translations);
+ }
+}
diff --git a/app/application/app/Models/ProjectLanguage.php b/app/application/app/Models/ProjectLanguage.php
index a7b8f77..b6268ff 100644
--- a/app/application/app/Models/ProjectLanguage.php
+++ b/app/application/app/Models/ProjectLanguage.php
@@ -2,7 +2,9 @@
namespace App\Models;
+use App\Enums\Lang;
use App\Models\Scopes\SortScope;
+use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -33,6 +35,8 @@ final class ProjectLanguage extends Model
'code',
'is_default',
'sort',
+ 'system_lang',
+ 'iso_code',
];
/**
@@ -43,8 +47,27 @@ final class ProjectLanguage extends Model
protected function casts(): array
{
return [
- 'is_default' => 'boolean',
- 'sort' => 'integer',
+ 'is_default' => 'boolean',
+ 'sort' => 'integer',
+ 'system_lang' => Lang::class,
];
}
+
+ protected function attributeLang(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+
+ if ($this->iso_code) {
+ return $this->iso_code;
+ }
+
+ if ($this->system_lang) {
+ return $this->system_lang->getLocale();
+ }
+
+ return $this->code;
+ },
+ )->shouldCache();
+ }
}
diff --git a/app/application/app/Models/ProjectTranslation.php b/app/application/app/Models/ProjectTranslation.php
new file mode 100644
index 0000000..4961a96
--- /dev/null
+++ b/app/application/app/Models/ProjectTranslation.php
@@ -0,0 +1,26 @@
+hasPermission('project.view');
+ }
+
+ public function view(User $user, ProjectTranslation $translation): bool
+ {
+ return $user->hasPermission('project-translation.view');
+ }
+
+ public function update(User $user): bool
+ {
+ return $user->hasPermission('project-translation.update');
+ }
+}
diff --git a/app/application/app/Repositories/ProjectTranslationRepository.php b/app/application/app/Repositories/ProjectTranslationRepository.php
new file mode 100644
index 0000000..10bce6a
--- /dev/null
+++ b/app/application/app/Repositories/ProjectTranslationRepository.php
@@ -0,0 +1,25 @@
+where('project_id', $projectId)->where('language_id', $languageId);
+ return $this->createSearchInstanceCommand->execute($query);
+ }
+
+ public function getProjectTranslationsWithTrashed(int $projectId, int $languageId): Search
+ {
+ $query = ProjectTranslation::query()->withTrashed()->where('project_id', $projectId)->where('language_id', $languageId);
+ return $this->createSearchInstanceCommand->execute($query);
+ }
+}
diff --git a/app/application/app/ServiceResults/Site/PagePossibleWithoutTranslation.php b/app/application/app/ServiceResults/Site/PagePossibleWithoutTranslation.php
index af3e03d..4ebc4fc 100644
--- a/app/application/app/ServiceResults/Site/PagePossibleWithoutTranslation.php
+++ b/app/application/app/ServiceResults/Site/PagePossibleWithoutTranslation.php
@@ -3,14 +3,14 @@
namespace app\ServiceResults\Site;
use App\Models\Project;
-use App\Models\ProjectLanguage;
use App\ServiceResults\ServiceResult;
+use App\Services\WebsiteTranslations;
final class PagePossibleWithoutTranslation extends ServiceResult
{
public function __construct(
private readonly Project $project,
- private readonly ProjectLanguage $language,
+ private readonly WebsiteTranslations $websiteTranslations,
private readonly array $data,
private readonly bool $isTranslation,
) { }
@@ -30,8 +30,8 @@ public function getProject(): Project
return $this->project;
}
- public function getLanguage(): ProjectLanguage
+ public function getWebsiteTranslations(): WebsiteTranslations
{
- return $this->language;
+ return $this->websiteTranslations;
}
}
diff --git a/app/application/app/Services/Admin/Project/TranslationService.php b/app/application/app/Services/Admin/Project/TranslationService.php
new file mode 100644
index 0000000..b8482e5
--- /dev/null
+++ b/app/application/app/Services/Admin/Project/TranslationService.php
@@ -0,0 +1,91 @@
+projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('viewAny', ProjectTranslation::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ return $this->result([
+ 'project' => $project,
+ ]);
+ }
+
+ public function edit(int $projectId, int $languageId, User $user): ServiceResultError | ServiceResultArray
+ {
+ $project = $this->projectRepository->getProjectById($projectId);
+ $language = $project?->languages()->firstWhere('id', $languageId);
+
+ if (\is_null($project) || \is_null($language)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('viewAny', ProjectTranslation::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ return $this->result([
+ 'project' => $project,
+ 'language' => $language,
+ 'projectTranslations' => $this->projectTranslationRepository->getProjectTranslations($projectId, $languageId)->all()->pluck('text', 'code')->toArray(),
+ 'translations' => Translations::getTranslationCodes(),
+ ]);
+ }
+
+ public function update(int $projectId, int $languageId, Update $data, User $user): ServiceResultError | ServiceResultSuccess
+ {
+ $project = $this->projectRepository->getProjectById($projectId);
+ $language = $project?->languages()->firstWhere('id', $languageId);
+
+ if (\is_null($project) || \is_null($language)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('update', ProjectTranslation::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ try {
+ DB::transaction(function () use ($data, $project, $language) {
+ $this->translationModelSyncCommand->execute($project, $language, $data->getTranslations());
+ });
+ $this->clearCacheCommandHandler->byTag(CacheTag::ProjectTranslation);
+ } catch (\Throwable $e) {
+ report($e);
+ return $this->errService(__('Server Error'));
+ }
+
+ return $this->ok(__('Translations successfully updated'));
+ }
+}
diff --git a/app/application/app/Services/ProjectLanguage/ModelSyncCommand.php b/app/application/app/Services/ProjectLanguage/ModelSyncCommand.php
index 51816fb..869363a 100644
--- a/app/application/app/Services/ProjectLanguage/ModelSyncCommand.php
+++ b/app/application/app/Services/ProjectLanguage/ModelSyncCommand.php
@@ -23,6 +23,8 @@ public function execute(Project $project, Languages $languages): void
'title' => $language->getTitle(),
'sort' => $language->getSort(),
'is_default' => $language->isDefault(),
+ 'system_lang' => $language->getSystemLang(),
+ 'iso_code' => $language->getIsoCode(),
];
if ($language->getId() !== null) {
diff --git a/app/application/app/Services/ProjectTranslation/ModelSyncCommand.php b/app/application/app/Services/ProjectTranslation/ModelSyncCommand.php
new file mode 100644
index 0000000..fce52d9
--- /dev/null
+++ b/app/application/app/Services/ProjectTranslation/ModelSyncCommand.php
@@ -0,0 +1,60 @@
+projectTranslationRepository->getProjectTranslationsWithTrashed($project->id, $language->id)->all();
+ $insert = [];
+ foreach ($update->getTranslations() as $translation) {
+ /** @var Translation $translation */
+ /** @var ProjectTranslation $modelTranslation */
+ $modelTranslation = $modelTranslations->firstWhere('code', $translation->getCode());
+
+ if ($modelTranslation === null && $translation->getText() === null) {
+ continue;
+ }
+
+ if ($modelTranslation === null && $translation->getText() !== null) {
+ $insert[] = [
+ 'created_at' => Carbon::now(),
+ 'updated_at' => Carbon::now(),
+ 'project_id' => $project->id,
+ 'language_id' => $language->id,
+ 'code' => $translation->getCode(),
+ 'text' => $translation->getText(),
+ ];
+ continue;
+ }
+
+ if ($modelTranslation !== null && $translation->getText() === null) {
+ $modelTranslation->delete();
+ continue;
+ }
+
+ if ($modelTranslation->trashed()) {
+ $modelTranslation->deleted_at = null;
+ }
+ $modelTranslation->text = $translation->getText();
+ $modelTranslation->save();
+ }
+
+ if (!empty($insert)) {
+ ProjectTranslation::query()->insert($insert);
+ }
+ }
+}
diff --git a/app/application/app/Services/Service.php b/app/application/app/Services/Service.php
index 44adf5e..7d77602 100644
--- a/app/application/app/Services/Service.php
+++ b/app/application/app/Services/Service.php
@@ -3,7 +3,6 @@
namespace App\Services;
use App\Models\Project;
-use App\Models\ProjectLanguage;
use App\ServiceResults\ServiceResultArray;
use App\ServiceResults\ServiceResultError;
use App\ServiceResults\ServiceResultSuccess;
@@ -11,7 +10,6 @@
use App\ServiceResults\StoreUpdateResult;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Response;
-use Illuminate\Support\Facades\Cache;
abstract class Service
{
@@ -60,9 +58,9 @@ final protected function result(array $data = []): ServiceResultArray
return new ServiceResultArray(data: $data);
}
- final protected function resultSitePage(Project $project, ProjectLanguage $language, array $data, bool $isTranslation): PagePossibleWithoutTranslation
+ final protected function resultSitePage(Project $project, WebsiteTranslations $websiteTranslations, array $data, bool $isTranslation): PagePossibleWithoutTranslation
{
- return new PagePossibleWithoutTranslation($project, $language, $data, $isTranslation);
+ return new PagePossibleWithoutTranslation($project, $websiteTranslations, $data, $isTranslation);
}
final protected function error(int $code, string $message, array $errors = []): ServiceResultError
diff --git a/app/application/app/Services/Site/ProjectService.php b/app/application/app/Services/Site/ProjectService.php
index 8cfb9c1..d39ff20 100644
--- a/app/application/app/Services/Site/ProjectService.php
+++ b/app/application/app/Services/Site/ProjectService.php
@@ -6,19 +6,18 @@
use App\Models\User;
use App\Dto\Builder\Project as ProjectBuilderDto;
use App\Repositories\ProjectContentRepository;
-use App\Repositories\ProjectLanguageRepository;
use App\Repositories\ProjectLinkRepository;
use App\Repositories\ProjectRepository;
use App\ServiceResults\ServiceResultArray;
use App\ServiceResults\ServiceResultError;
use App\ServiceResults\Site\PagePossibleWithoutTranslation;
use App\Services\Service;
+use App\Services\WebsiteTranslations;
final class ProjectService extends Service
{
public function __construct(
private readonly ProjectRepository $projectRepository,
- private readonly ProjectLanguageRepository $projectLanguageRepository,
private readonly ProjectContentRepository $projectContentRepository,
private readonly ProjectLinkRepository $projectLinkRepository,
) { }
@@ -39,7 +38,7 @@ public function getProjects(?User $user, array $with): ServiceResultError | Serv
]);
}
- public function getAboutByProject(Project $project, ?string $languageCode, ?User $user = null): ServiceResultError | PagePossibleWithoutTranslation
+ public function getAboutByProject(Project $project, WebsiteTranslations $websiteTranslations, ?User $user = null): ServiceResultError | PagePossibleWithoutTranslation
{
if (
$project->is_public === false
@@ -47,17 +46,13 @@ public function getAboutByProject(Project $project, ?string $languageCode, ?User
) {
return $this->errFobidden(__('Access is denied'));
}
- $language = $this->projectLanguageRepository->getProjectLanguageByCodeOrDefault($project, $languageCode);
- if (!$language) {
- return $this->errNotFound(__('Language not found'));
- }
$data = [
'project' => $project,
- 'language' => $language,
- 'content' => $this->projectContentRepository->getContentByLanguageId($project->id, $language->id),
- 'links' => $this->projectLinkRepository->getLinksByProject($project, $language->id),
+ 'websiteTranslations' => $websiteTranslations,
+ 'content' => $this->projectContentRepository->getContentByLanguageId($project->id, $websiteTranslations->getLanguage()->id),
+ 'links' => $this->projectLinkRepository->getLinksByProject($project, $websiteTranslations->getLanguage()->id),
];
- return $this->resultSitePage($project, $language, $data, \is_null($data['content']));
+ return $this->resultSitePage($project, $websiteTranslations, $data, \is_null($data['content']));
}
}
diff --git a/app/application/app/Services/WebsiteTranslations.php b/app/application/app/Services/WebsiteTranslations.php
new file mode 100644
index 0000000..3cf633f
--- /dev/null
+++ b/app/application/app/Services/WebsiteTranslations.php
@@ -0,0 +1,23 @@
+transactions[$text] ?? __($text);
+ }
+
+ public function getLanguage(): ProjectLanguage
+ {
+ return $this->language;
+ }
+}
diff --git a/app/application/app/View/Components/Site/ChooseLanguage.php b/app/application/app/View/Components/Site/ChooseLanguage.php
index 58e2647..17c78b5 100644
--- a/app/application/app/View/Components/Site/ChooseLanguage.php
+++ b/app/application/app/View/Components/Site/ChooseLanguage.php
@@ -2,8 +2,9 @@
namespace app\View\Components\Site;
-use App\Models\ProjectLanguage;
-use Illuminate\Support\Collection;
+use App\Enums\CacheTag;
+use App\Models\Project;
+use App\Services\WebsiteTranslations;
use Illuminate\Support\Str;
use Illuminate\View\Component;
use Illuminate\View\View;
@@ -11,20 +12,25 @@
final class ChooseLanguage extends Component
{
public function __construct(
- private readonly ProjectLanguage $language,
- private readonly Collection $languages,
+ private readonly WebsiteTranslations $websiteTranslations,
+ private readonly Project $project,
) { }
public function render(): View
{
- $link = Str::of( request()->getRequestUri() )->rtrim('/');
- if ($link->endsWith('/language/' . $this->language->code)) {
- $link = $link->replace('/language/' . $this->language->code, '', false);
+ $link = Str::of( request()->url() )->rtrim('/');
+ if ($link->endsWith('/language/' . $this->websiteTranslations->getLanguage()->code)) {
+ $link = $link->replace('/language/' . $this->websiteTranslations->getLanguage()->code, '', false);
}
+ $seconds = 3600 * 12;
+ $languages = CacheTag::Project->getCache()->remember(self::class . $this->project->id, $seconds, function () {
+ return $this->project->languages;
+ });
+
return view('components.site.choose-language', [
- 'language' => $this->language,
- 'languages' => $this->languages,
+ 'websiteTranslations' => $this->websiteTranslations,
+ 'languages' => $languages,
'link' => (string) $link,
]);
}
diff --git a/app/application/app/View/Components/Site/Layout.php b/app/application/app/View/Components/Site/Layout.php
index a0f13f5..2a99cb4 100644
--- a/app/application/app/View/Components/Site/Layout.php
+++ b/app/application/app/View/Components/Site/Layout.php
@@ -4,7 +4,7 @@
use App\Enums\StorageType;
use App\Models\Project;
-use App\Models\ProjectLanguage;
+use App\Services\WebsiteTranslations;
use Illuminate\View\Component;
use Illuminate\View\View;
@@ -12,7 +12,7 @@ final class Layout extends Component
{
public function __construct(
private readonly Project $project,
- private readonly ProjectLanguage $language,
+ private readonly WebsiteTranslations $websiteTranslations,
) { }
public function render(): View
@@ -20,7 +20,7 @@ public function render(): View
return view('layout.site', [
'project' => $this->project,
'logo' => $this->project->getStorageOne(StorageType::Logo),
- 'language' => $this->language,
+ 'websiteTranslations' => $this->websiteTranslations,
]);
}
}
diff --git a/app/application/app/View/Components/Volt/Forms/Languages.php b/app/application/app/View/Components/Volt/Forms/Languages.php
index a4c3e41..f422d6f 100644
--- a/app/application/app/View/Components/Volt/Forms/Languages.php
+++ b/app/application/app/View/Components/Volt/Forms/Languages.php
@@ -29,12 +29,18 @@ private function getValue(): array
$default = (int) $default;
}
foreach ($value['items'] as $index => $lang) {
+ $systemLang = null;
+ if (!empty($lang['system_lang'])) {
+ $systemLang = (int) $lang['system_lang'];
+ }
$langs[$index] = [
- 'title' => $lang['title'] ?? '',
- 'code' => $lang['code'] ?? '',
- 'is_default' => ($index === $default),
- 'sort' => $lang['sort'] ?? '',
- 'id' => $lang['id'] ?? null,
+ 'title' => $lang['title'] ?? '',
+ 'code' => $lang['code'] ?? '',
+ 'is_default' => ($index === $default),
+ 'sort' => $lang['sort'] ?? '',
+ 'system_lang' => $systemLang,
+ 'iso_code' => $lang['iso_code'] ?? null,
+ 'id' => $lang['id'] ?? null,
];
}
diff --git a/app/application/bootstrap/app.php b/app/application/bootstrap/app.php
index f877115..1f526b3 100644
--- a/app/application/bootstrap/app.php
+++ b/app/application/bootstrap/app.php
@@ -11,11 +11,8 @@
commands: __DIR__.'/../routes/console.php',
health: '/up',
then: function () {
- Route::middleware([\App\Http\Middleware\ProjectDomain::class])->group(base_path('routes/web-project.php'));
- Route::middleware([
- \App\Http\Middleware\ProjectDomain::class,
- \App\Http\Middleware\Project::class,
- ])
+ Route::middleware(['web', \App\Http\Middleware\ProjectDomainAndLanguage::class])->group(base_path('routes/web-project.php'));
+ Route::middleware(['web', \App\Http\Middleware\ProjectAndLanguage::class])
->prefix('project/{project}')
->as('project.')
->group(base_path('routes/web-project.php'));
diff --git a/app/application/database/migrations/2024_04_18_144547_add_system_language_to_project_languages_table.php b/app/application/database/migrations/2024_04_18_144547_add_system_language_to_project_languages_table.php
new file mode 100644
index 0000000..46f4f29
--- /dev/null
+++ b/app/application/database/migrations/2024_04_18_144547_add_system_language_to_project_languages_table.php
@@ -0,0 +1,30 @@
+unsignedInteger('system_lang')->nullable()->after('sort');
+ $table->string('iso_code', 30)->nullable()->after('system_lang');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('project_languages', function (Blueprint $table) {
+ $table->dropColumn('system_lang');
+ $table->dropColumn('iso_code');
+ });
+ }
+};
diff --git a/app/application/database/migrations/2024_04_19_084614_create_project_translations.php b/app/application/database/migrations/2024_04_19_084614_create_project_translations.php
new file mode 100644
index 0000000..867deb3
--- /dev/null
+++ b/app/application/database/migrations/2024_04_19_084614_create_project_translations.php
@@ -0,0 +1,36 @@
+id();
+ $table->string('code');
+ $table->text('text');
+ $table->unsignedBigInteger('project_id')->index();
+ $table->foreign('project_id')->references('id')->on('projects');
+ $table->unsignedBigInteger('language_id')->index();
+ $table->foreign('language_id')->references('id')->on('project_languages');
+ $table->timestamps();
+ $table->softDeletes();
+ $table->index(['project_id', 'language_id', 'deleted_at']);
+ $table->unique(['project_id', 'language_id', 'code']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('project_translations');
+ }
+};
diff --git a/app/application/lang/en.json b/app/application/lang/en.json
index 1e550c9..8376977 100644
--- a/app/application/lang/en.json
+++ b/app/application/lang/en.json
@@ -255,5 +255,6 @@
"The link was successfully updated": "The link was successfully updated",
"The link has been deleted": "The link has been deleted",
"Language not found": "Language not found",
- "Project not found": "Project not found"
+ "Project not found": "Project not found",
+ "Translations successfully updated": "Translations successfully updated"
}
diff --git a/app/application/lang/en/admin-sections.php b/app/application/lang/en/admin-sections.php
index 946a4c3..a4283b1 100644
--- a/app/application/lang/en/admin-sections.php
+++ b/app/application/lang/en/admin-sections.php
@@ -11,4 +11,5 @@
'Languages' => 'Languages',
'Last update' => 'Last update',
'Links project' => 'Links from the project',
+ 'Translations' => 'Translations',
];
diff --git a/app/application/lang/en/permissions.php b/app/application/lang/en/permissions.php
index 88b02d2..8800e50 100644
--- a/app/application/lang/en/permissions.php
+++ b/app/application/lang/en/permissions.php
@@ -15,4 +15,5 @@
'Project' => 'Projects',
'ProjectContent' => 'About the project',
'ProjectLink' => 'Links from the project',
+ 'ProjectTranslation' => 'Translations',
];
diff --git a/app/application/lang/en/validation.php b/app/application/lang/en/validation.php
index 11fdad1..90940c1 100644
--- a/app/application/lang/en/validation.php
+++ b/app/application/lang/en/validation.php
@@ -281,11 +281,15 @@
'is_public' => 'public',
'http_host' => 'hostname',
'sort' => 'sorting',
+ 'system_lang' => 'system language',
+ 'iso_code' => 'ISO code',
'is_default' => 'default',
'language-code' => 'language code',
'languages.items.*.code' => 'language code',
'languages.items.*.title' => 'language title',
'languages.items.*.sort' => 'language sorting',
+ 'languages.items.*.system_lang' => 'system language',
+ 'languages.items.*.iso_code' => 'ISO code',
'languages.items.*.id' => 'language ID',
'languages.default' => 'default language',
'language-default' => 'default language',
@@ -294,5 +298,8 @@
'logo.delete' => 'remove logo',
'link' => 'link',
'language_id' => 'language',
+ 'translations' => 'translations',
+ 'translations.*.code' => 'translations code',
+ 'translations.*.text' => 'translations',
],
];
diff --git a/app/application/lang/ru.json b/app/application/lang/ru.json
index c513d72..2f1f239 100644
--- a/app/application/lang/ru.json
+++ b/app/application/lang/ru.json
@@ -255,5 +255,6 @@
"The link was successfully updated": "Ссылка успешно обновлена",
"The link has been deleted": "Ссылка удалена",
"Language not found": "Язык не найден",
- "Project not found": "Проект не найден"
+ "Project not found": "Проект не найден",
+ "Translations successfully updated": "Переводы успешно обновлены"
}
diff --git a/app/application/lang/ru/admin-sections.php b/app/application/lang/ru/admin-sections.php
index 5ec768a..d8751b7 100644
--- a/app/application/lang/ru/admin-sections.php
+++ b/app/application/lang/ru/admin-sections.php
@@ -11,4 +11,5 @@
'Languages' => 'Языки',
'Last update' => 'Последнее обновление',
'Links project' => 'Ссылки от проекта',
+ 'Translations' => 'Переводы',
];
diff --git a/app/application/lang/ru/permissions.php b/app/application/lang/ru/permissions.php
index dfe8cd7..03d89bf 100644
--- a/app/application/lang/ru/permissions.php
+++ b/app/application/lang/ru/permissions.php
@@ -15,4 +15,5 @@
'Project' => 'Проекты',
'ProjectContent' => 'О проекте',
'ProjectLink' => 'Ссылки от проекта',
+ 'ProjectTranslation' => 'Переводы',
];
diff --git a/app/application/lang/ru/validation.php b/app/application/lang/ru/validation.php
index 5f48b83..d6865b9 100644
--- a/app/application/lang/ru/validation.php
+++ b/app/application/lang/ru/validation.php
@@ -282,10 +282,14 @@
'http_host' => 'имя хоста',
'sort' => 'сортировка',
'is_default' => 'по умолчанию',
+ 'system_lang' => 'системный язык',
+ 'iso_code' => 'ISO-код',
'language-code' => 'код языка',
'languages.items.*.code' => 'код языка',
'languages.items.*.title' => 'заголовок языка',
'languages.items.*.sort' => 'языковая сортировка',
+ 'languages.items.*.system_lang' => 'системный язык',
+ 'languages.items.*.iso_code' => 'ISO-код',
'languages.items.*.id' => 'ID языка',
'languages.default' => 'язык по умолчанию',
'language-default' => 'язык по умолчанию',
@@ -294,5 +298,8 @@
'logo.delete' => 'удалить логотип',
'link' => 'ссылка',
'language_id' => 'язык',
+ 'translations' => 'переводы',
+ 'translations.*.code' => 'код перевода',
+ 'translations.*.text' => 'переводы',
],
];
diff --git a/app/application/resources/site/scss/app.scss b/app/application/resources/site/scss/app.scss
index e032aa3..0b3f177 100644
--- a/app/application/resources/site/scss/app.scss
+++ b/app/application/resources/site/scss/app.scss
@@ -49,7 +49,7 @@ body {
}
.header_logo {
grid-area: logo;
- max-height: 50px;
+ height: 50px;
a {
text-decoration: none;
@@ -67,6 +67,7 @@ body {
padding: 5px 10px;
border: 1px solid #0a0e17;
border-radius: 4px;
+ white-space: nowrap;
}
}
#mobile-menu {
@@ -231,12 +232,24 @@ body.mobile-menu-open {
grid-template-areas: "logo language";
grid-template-columns: 1fr 200px;
}
+ .header_logo {
+ width: 150px;
+ margin-left: 12px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ }
#mobile-menu {
display: none;
}
.main-container {
flex-direction: row;
}
+ #language {
+ margin-right: 10px;
+ }
+ .language__block {
+ margin-top: 6px;
+ }
.language__button {
padding: 7px 10px 7px 0;
}
diff --git a/app/application/resources/views/admin/projects/links/edit.blade.php b/app/application/resources/views/admin/projects/links/edit.blade.php
index 4d2bc86..189a52a 100644
--- a/app/application/resources/views/admin/projects/links/edit.blade.php
+++ b/app/application/resources/views/admin/projects/links/edit.blade.php
@@ -1,7 +1,7 @@
@section('meta_title', __('admin-sections.Links project'))
@section('h1', __('admin-sections.Project') . ': ' . $project->name)
- @include('admin.projects._top')
+ @include('admin.projects.links._top')
diff --git a/app/application/resources/views/admin/projects/show.blade.php b/app/application/resources/views/admin/projects/show.blade.php
index 59e8ebc..fe9ecbe 100644
--- a/app/application/resources/views/admin/projects/show.blade.php
+++ b/app/application/resources/views/admin/projects/show.blade.php
@@ -36,6 +36,18 @@
|
@endcan
+ @can('viewAny', \App\Models\ProjectTranslation::class)
+