Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft] [Example] Integrate a webspace specific setting administration interface #106

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8,984 changes: 8,984 additions & 0 deletions assets/admin/package-lock.json

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions config/forms/webspace_setting_details.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" ?>
<form xmlns="http://schemas.sulu.io/template/template"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/form-1.0.xsd"
>
<key>webspace_setting_details</key>

<properties>
<property name="demobarText" type="text_line">
<meta>
<title>app.demobar_text</title>
</meta>
</property>
</properties>
</form>
5 changes: 5 additions & 0 deletions config/packages/app_webspace_setting_admin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
sulu_admin:
resources:
webspace_settings:
routes:
detail: 'app.get_webspace_settings'

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions public/build/admin/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"main.css": "/build/admin/main.7825759723a3c825efc7.css",
"main.js": "/build/admin/main.7825759723a3c825efc7.js",
"main.js.map": "/build/admin/main.7825759723a3c825efc7.js.map",
"main.css": "/build/admin/main.a5f695a5931373b4bce3.css",
"main.js": "/build/admin/main.a5f695a5931373b4bce3.js",
"main.js.map": "/build/admin/main.a5f695a5931373b4bce3.js.map",
"build/admin/fonts/fa-brands-400.woff": "/build/admin/fonts/fa-brands-400.2285773e6b4b172f07d9b777c81b0775.woff",
"build/admin/fonts/fa-brands-400.eot": "/build/admin/fonts/fa-brands-400.23f19bb08961f37aaf692ff943823453.eot",
"build/admin/fonts/fa-brands-400.svg": "/build/admin/fonts/fa-brands-400.2f517e09eb2ca6650ff5bec5a95157ab.svg",
Expand Down
125 changes: 125 additions & 0 deletions src/Admin/WebspaceSettingAdmin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

declare(strict_types=1);

namespace App\Admin;

use App\Entity\WebspaceSetting;
use Sulu\Bundle\AdminBundle\Admin\Admin;
use Sulu\Bundle\AdminBundle\Admin\View\ToolbarAction;
use Sulu\Bundle\AdminBundle\Admin\View\ViewBuilderFactoryInterface;
use Sulu\Bundle\AdminBundle\Admin\View\ViewCollection;
use Sulu\Bundle\PageBundle\Admin\PageAdmin;
use Sulu\Component\Security\Authorization\PermissionTypes;
use Sulu\Component\Security\Authorization\SecurityCheckerInterface;
use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
use Sulu\Component\Webspace\Webspace;

class WebspaceSettingAdmin extends Admin
{
public const TAB_VIEW = 'app.webspace_settings';
public const FORM_VIEW = 'app.webspace_settings.form';
public const FORM_KEY = 'webspace_setting_details';


public function __construct(
private WebspaceManagerInterface $webspaceManager,
private ViewBuilderFactoryInterface $viewBuilderFactory,
private SecurityCheckerInterface $securityChecker
) {
}

public function configureViews(ViewCollection $viewCollection): void
{
if ($this->hasSomeWebspaceSettingPermission()) {
$viewCollection->add(
// sulu will only load the existing entity if the path of the form includes an id attribute
$this->viewBuilderFactory->createResourceTabViewBuilder(static::TAB_VIEW, '/webspace-settings/:id')
->setResourceKey(WebspaceSetting::RESOURCE_KEY)
->setTabTitle('app.webspace_settings')
->setParent(PageAdmin::WEBSPACE_TABS_VIEW)
->setOption('routerAttributesToFormRequest', ['webspace'])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be get part of the createResourceTabViewBuilder as addRouterAttributesToFormRequest when implemented

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be get part of the createResourceTabViewBuilder as addRouterAttributesToFormRequest when implemented

Is there anything planed to get the webspace in createResourceTabViewBuilder?
THX for info

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Builder is generally for all webspaces. To get the webspace in the endpoint which this view is for you need $request->query->get('webspace');. If you want to hide a tab you may could try use tabCondition but I'm not sure which data is available in the condition itself.

Copy link

@hual7 hual7 Jun 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something is different in the admin build, so I run in a permission view error because I didn't get the webspace key with $request->query->get('webspace')
Is there any sulu js extension in the example, that is not loaded via bin/console sulu:admin:update-build?
THX

->setTabOrder(8096)
->setAttributeDefault('id', '-')
);

$viewCollection->add(
$this->viewBuilderFactory->createFormViewBuilder(static::FORM_VIEW, '/details')
->setResourceKey(WebspaceSetting::RESOURCE_KEY)
->setFormKey(self::FORM_KEY)
->setTabTitle('sulu_admin.details')
->addToolbarActions([new ToolbarAction('sulu_admin.save')])
->addRouterAttributesToFormRequest(['webspace'])
->setParent(static::TAB_VIEW)
);
}
}

public function getSecurityContexts(): array
{
$webspaceContexts = [];
/* @var Webspace $webspace */
foreach ($this->webspaceManager->getWebspaceCollection() as $webspace) {
$securityContextKey = self::getWebspaceSettingSecurityContext($webspace->getKey());
$webspaceContexts[$securityContextKey] = $this->getSecurityContextPermissions();
}

return [
self::SULU_ADMIN_SECURITY_SYSTEM => [
'Webspaces' => $webspaceContexts,
],
];
}

public function getSecurityContextsWithPlaceholder(): array
{
return [
self::SULU_ADMIN_SECURITY_SYSTEM => [
'Webspaces' => [
self::getWebspaceSettingSecurityContext('#webspace#') => $this->getSecurityContextPermissions(),
],
],
];
}

/**
* Returns security context for settings in given webspace.
*
* @final
*
* @param string $webspaceKey
*
* @return string
*/
public static function getWebspaceSettingSecurityContext($webspaceKey): string
{
return \sprintf('%s%s.%s', PageAdmin::SECURITY_CONTEXT_PREFIX, $webspaceKey, 'settings');
}

/**
* @return string[]
*/
private function getSecurityContextPermissions(): array
{
return [
PermissionTypes::VIEW,
PermissionTypes::EDIT,
];
}

private function hasSomeWebspaceSettingPermission(): bool
{
foreach ($this->webspaceManager->getWebspaceCollection()->getWebspaces() as $webspace) {
$hasWebspaceAnalyticsPermission = $this->securityChecker->hasPermission(
self::getWebspaceSettingSecurityContext($webspace->getKey()),
PermissionTypes::EDIT
);

if ($hasWebspaceAnalyticsPermission) {
return true;
}
}

return false;
}
}
97 changes: 97 additions & 0 deletions src/Controller/Admin/WebspaceSettingController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

declare(strict_types=1);

namespace App\Controller\Admin;

use App\Admin\WebspaceSettingAdmin;
use App\Entity\WebspaceSetting;
use Doctrine\ORM\EntityManagerInterface;
use Sulu\Component\Security\SecuredControllerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
* @phpstan-type WebspaceSettingsData array{
* demobarText: string|null,
* }
*/
class WebspaceSettingController extends AbstractController implements SecuredControllerInterface
{
public function __construct(
private EntityManagerInterface $entityManager,
private RequestStack $requestStack,
) {
}

/**
* @Route("/admin/api/webspace-settings/{id}", methods={"GET"}, name="app.get_webspace_settings")
*/
public function getAction(Request $request): Response
{
$webspace = $request->query->get('webspace');

$webspaceSetting = $this->entityManager->getRepository(WebspaceSetting::class)->findOneBy([
'webspace' => $webspace,
]);

return $this->json($this->getDataForEntity($webspaceSetting ?: new WebspaceSetting($webspace)));
}

/**
* @Route("/admin/api/webspace-settings/{id}", methods={"PUT"}, name="app.put_webspace_settings")
*/
public function putAction(Request $request): Response
{
$webspace = $request->query->get('webspace');

$webspaceSetting = $this->entityManager->getRepository(WebspaceSetting::class)->findOneBy([
'webspace' => $webspace,
]);

if (!$webspaceSetting) {
$webspaceSetting = new WebspaceSetting($webspace);
$this->entityManager->persist($webspaceSetting);
}

/** @var WebspaceSettingsData $data */
$data = $request->toArray();
$this->mapDataToEntity($data, $webspaceSetting);
$this->entityManager->flush();

return $this->json($this->getDataForEntity($webspaceSetting));
}

/**
* @return WebspaceSettingsData $data
*/
protected function getDataForEntity(WebspaceSetting $entity): array
{
return [
'demobarText' => $entity->getDemobarText(),
];
}

/**
* @param WebspaceSettingsData $data
*/
protected function mapDataToEntity(array $data, WebspaceSetting $entity): void
{
$entity->setDemobarText($data['demobarText']);
}

public function getSecurityContext()
{
$request = $this->requestStack->getCurrentRequest();

return WebspaceSettingAdmin::getWebspaceSettingSecurityContext($request->query->get('webspace'));
}

public function getLocale(Request $request): ?string
{
return $request->query->get('locale');
}
}
53 changes: 53 additions & 0 deletions src/Entity/WebspaceSetting.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sulu\Component\Persistence\Model\AuditableInterface;
use Sulu\Component\Persistence\Model\AuditableTrait;


#[ORM\Entity]
#[ORM\Table(name: 'app_webspace_setting')]
class WebspaceSetting implements AuditableInterface
{
use AuditableTrait;

public const RESOURCE_KEY = 'webspace_settings';

#[ORM\Id()]
#[ORM\GeneratedValue()]
#[ORM\Column(type: "integer")]
private ?int $id = null;

#[ORM\Column(type: "string", length: 32, nullable: false, unique: true)]
private string $webspace;

#[ORM\Column(type: "string", length: 191, nullable: true)]
private ?string $demobarText = null;

public function __construct(string $webspace)
{
$this->webspace = $webspace;
}

public function getId(): ?int
{
return $this->id;
}

public function getWebspace(): string
{
return $this->webspace;
}

public function getDemobarText(): ?string
{
return $this->demobarText;
}

public function setDemobarText(?string $demobarText): void
{
$this->demobarText = $demobarText;
}
}
33 changes: 33 additions & 0 deletions src/Twig/WebspaceSettingTwigExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace App\Twig;

use App\Entity\WebspaceSetting;
use Doctrine\ORM\EntityManagerInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class WebspaceSettingTwigExtension extends AbstractExtension
{
private EntityManagerInterface $entityManager;

public function __construct(
EntityManagerInterface $entityManager
) {
$this->entityManager = $entityManager;
}

public function getFunctions()
{
return [
new TwigFunction('load_webspace_setting', [$this, 'loadWebspaceSetting']),
];
}

public function loadWebspaceSetting(string $webspace): WebspaceSetting
{
$applicationSettings = $this->entityManager->getRepository(WebspaceSetting::class)->findOneBy(['webspace' => $webspace]) ?? null;

return $applicationSettings ?: new WebspaceSetting($webspace);
}
}
2 changes: 2 additions & 0 deletions templates/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
{# @see https://docs.sulu.io/en/2.2/reference/twig-extensions/functions/sulu_snippet_load_by_area.html #}
{% set webspaceSettings = sulu_snippet_load_by_area('webspace_settings') %}

{% set webspaceSetting = load_webspace_setting(app.request.attributes.get('_sulu').getAttribute('webspace').key) %}

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
Expand Down
2 changes: 1 addition & 1 deletion templates/includes/demobar.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</a>

<p class="demobar__title">
SULU DEMO
{{ applicationSettings.demobarText|default('SULU DEMO') }}
</p>

<a href="https://github.com/sulu/sulu-demo" target="_blank" class="demobar__github">
Expand Down
4 changes: 3 additions & 1 deletion translations/admin.de.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@
"app.album_selection_label": "{count} {count, plural, =1 {Album} other {Alben}} ausgewählt",
"app.select_albums": "Alben auswählen",
"app.no_album_selected": "Kein Album ausgewählt",
"app.select_album": "Album auswählen"
"app.select_album": "Album auswählen",
"app.webspace_settings": "Einstellungen",
"app.demobar_text": "Demobar Text"
}
4 changes: 3 additions & 1 deletion translations/admin.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@
"app.album_selection_label": "{count} {count, plural, =1 {album} other {albums}} selected",
"app.select_albums": "Select albums",
"app.no_album_selected": "No album selected",
"app.select_album": "Select album"
"app.select_album": "Select album",
"app.webspace_settings": "Settings",
"app.demobar_text": "Demobar Text"
}