Skip to content

Commit

Permalink
IBX-6645: As the User I want to change my data and avatar in User pro…
Browse files Browse the repository at this point in the history
…file (#1020)
  • Loading branch information
adamwojs authored Dec 13, 2023
1 parent cda6806 commit a4f9a3f
Show file tree
Hide file tree
Showing 36 changed files with 1,273 additions and 23 deletions.
10 changes: 5 additions & 5 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,11 @@ parameters:
count: 1
path: src/bundle/Controller/User/InvitationController.php

-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\ClickableInterface\\|Symfony\\\\Component\\\\Form\\\\FormInterface\\:\\:getName\\(\\)\\.$#"
count: 1
path: src/bundle/Controller/User/ProfileEditController.php

-
message: "#^Cannot access property \\$id on Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Content\\\\ContentInfo\\|null\\.$#"
count: 1
Expand Down Expand Up @@ -8170,11 +8175,6 @@ parameters:
count: 1
path: src/lib/Menu/UserMenuBuilder.php

-
message: "#^Method Ibexa\\\\AdminUi\\\\Menu\\\\UserMenuBuilder\\:\\:getTranslationMessages\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/lib/Menu/UserMenuBuilder.php

-
message: "#^Method Ibexa\\\\AdminUi\\\\Menu\\\\UserPasswordChangeRightSidebarBuilder\\:\\:createStructure\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
count: 1
Expand Down
141 changes: 141 additions & 0 deletions src/bundle/Controller/User/ProfileEditController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Bundle\AdminUi\Controller\User;

use Ibexa\AdminUi\Specification\UserProfile\IsProfileAvailable;
use Ibexa\AdminUi\UserProfile\UserProfileConfigurationInterface;
use Ibexa\ContentForms\Data\Mapper\UserUpdateMapper;
use Ibexa\ContentForms\Form\ActionDispatcher\ActionDispatcherInterface;
use Ibexa\ContentForms\Form\Type\User\UserUpdateType;
use Ibexa\ContentForms\User\View\UserUpdateView;
use Ibexa\Contracts\AdminUi\Controller\Controller;
use Ibexa\Contracts\ContentForms\Content\Form\Provider\GroupedContentFormFieldsProviderInterface;
use Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException;
use Ibexa\Contracts\Core\Repository\LanguageService;
use Ibexa\Contracts\Core\Repository\LocationService;
use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\Core\Repository\Repository;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\Core\Repository\Values\Content\Field;
use Ibexa\Contracts\Core\Repository\Values\Content\Location;
use Ibexa\Contracts\Core\Repository\Values\User\User;
use Ibexa\Core\FieldType\User\Type as UserFieldType;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;

final class ProfileEditController extends Controller
{
private Repository $repository;

private UserService $userService;

private LocationService $locationService;

private UserProfileConfigurationInterface $configuration;

private PermissionResolver $permissionResolver;

private LanguageService $languageService;

private ActionDispatcherInterface $userActionDispatcher;

private GroupedContentFormFieldsProviderInterface $groupedContentFormFieldsProvider;

public function __construct(
Repository $repository,
UserService $userService,
LocationService $locationService,
UserProfileConfigurationInterface $configuration,
PermissionResolver $permissionResolver,
LanguageService $languageService,
ActionDispatcherInterface $userActionDispatcher,
GroupedContentFormFieldsProviderInterface $groupedContentFormFieldsProvider
) {
$this->repository = $repository;
$this->userService = $userService;
$this->locationService = $locationService;
$this->configuration = $configuration;
$this->permissionResolver = $permissionResolver;
$this->languageService = $languageService;
$this->userActionDispatcher = $userActionDispatcher;
$this->groupedContentFormFieldsProvider = $groupedContentFormFieldsProvider;
}

/**
* @return \Ibexa\ContentForms\User\View\UserUpdateView|\Symfony\Component\HttpFoundation\Response
*/
public function editAction(Request $request, ?string $languageCode)
{
$user = $this->userService->loadUser($this->permissionResolver->getCurrentUserReference()->getUserId());
if (!$this->isUserProfileAvailable($user)) {
throw $this->createNotFoundException();
}

if (!$this->permissionResolver->canUser('user', 'selfedit', $user)) {
throw $this->createAccessDeniedException();
}

$languageCode ??= $user->contentInfo->mainLanguageCode;

$data = (new UserUpdateMapper())->mapToFormData($user, $user->getContentType(), [
'languageCode' => $languageCode,
'filter' => static fn (Field $field): bool => $field->fieldTypeIdentifier !== UserFieldType::FIELD_TYPE_IDENTIFIER,
]);

$form = $this->createForm(
UserUpdateType::class,
$data,
[
'languageCode' => $languageCode,
'mainLanguageCode' => $user->contentInfo->mainLanguageCode,
]
);

$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid() && $form instanceof Form && null !== $form->getClickedButton()) {
$this->userActionDispatcher->dispatchFormAction($form, $data, $form->getClickedButton()->getName());
if ($response = $this->userActionDispatcher->getResponse()) {
return $response;
}
}

$location = $this->repository->sudo(
fn (): Location => $this->locationService->loadLocation(
(int)$user->versionInfo->contentInfo->mainLocationId
)
);

$parentLocation = null;
try {
$parentLocation = $this->locationService->loadLocation($location->parentLocationId);
} catch (UnauthorizedException $e) {
}

return new UserUpdateView(
null,
[
'form' => $form->createView(),
'language_code' => $languageCode,
'language' => $this->languageService->loadLanguage($languageCode),
'content_type' => $user->getContentType(),
'user' => $user,
'location' => $location,
'parent_location' => $parentLocation,
'grouped_fields' => $this->groupedContentFormFieldsProvider->getGroupedFields(
$form->get('fieldsData')->all()
),
]
);
}

private function isUserProfileAvailable(User $user): bool
{
return (new IsProfileAvailable($this->configuration))->isSatisfiedBy($user);
}
}
101 changes: 101 additions & 0 deletions src/bundle/Controller/User/ProfileViewController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Bundle\AdminUi\Controller\User;

use Ibexa\AdminUi\Specification\UserProfile\IsProfileAvailable;
use Ibexa\AdminUi\UserProfile\UserProfileConfigurationInterface;
use Ibexa\Contracts\AdminUi\Controller\Controller;
use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\Core\Repository\Repository;
use Ibexa\Contracts\Core\Repository\RoleService;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\Core\Repository\Values\User\Role;
use Ibexa\Contracts\Core\Repository\Values\User\User;
use Symfony\Component\HttpFoundation\Response;

final class ProfileViewController extends Controller
{
private Repository $repository;

private UserService $userService;

private RoleService $roleService;

private PermissionResolver $permissionResolver;

private UserProfileConfigurationInterface $configuration;

public function __construct(
Repository $repository,
UserService $userService,
RoleService $roleService,
PermissionResolver $permissionResolver,
UserProfileConfigurationInterface $configuration
) {
$this->repository = $repository;
$this->userService = $userService;
$this->roleService = $roleService;
$this->permissionResolver = $permissionResolver;
$this->configuration = $configuration;
}

public function viewAction(int $userId): Response
{
$user = $this->userService->loadUser($userId);
if (!$this->isUserProfileAvailable($user)) {
throw $this->createNotFoundException();
}

$canEditProfile = $this->permissionResolver->canUser('user', 'selfedit', $user);

return $this->render(
'@ibexadesign/account/profile/view.html.twig',
[
'user' => $user,
'roles' => $this->getUserRoles($user),
'field_groups' => $this->configuration->getFieldGroups(),
'can_edit_profile' => $canEditProfile,
]
);
}

/**
* @return \Ibexa\Contracts\Core\Repository\Values\User\Role[]
*/
private function getUserRoles(User $user): iterable
{
if ($this->permissionResolver->hasAccess('role', 'read') !== true) {
return [];
}

/** @var \Ibexa\Contracts\Core\Repository\Values\User\RoleAssignment[] $assignments */
$assignments = $this->repository->sudo(function () use ($user): iterable {
return $this->roleService->getRoleAssignmentsForUser($user, true);
});

$roles = [];
foreach ($assignments as $assignment) {
$role = $assignment->getRole();
if (!array_key_exists($role->id, $roles)) {
$roles[$role->id] = $role;
}
}

usort($roles, static function (Role $roleA, Role $roleB): int {
return strcmp($roleA->identifier, $roleB->identifier);
});

return $roles;
}

private function isUserProfileAvailable(User $user): bool
{
return (new IsProfileAvailable($this->configuration))->isSatisfiedBy($user);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Bundle\AdminUi\DependencyInjection\Configuration\Parser;

use Ibexa\Bundle\Core\DependencyInjection\Configuration\AbstractParser;
use Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\ContextualizerInterface;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;

final class UserProfile extends AbstractParser
{
public function addSemanticConfig(NodeBuilder $nodeBuilder): void
{
$nodeBuilder
->arrayNode('user_profile')
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')
->defaultFalse()
->end()
->arrayNode('content_types')
->scalarPrototype()->end()
->defaultValue(['editor'])
->example(['editor', 'administrator'])
->end()
->arrayNode('field_groups')
->defaultValue(['about', 'contact'])
->scalarPrototype()->end()
->example(['about', 'contact'])
->end()
->end()
->end();
}

/**
* @param array<string, mixed> $scopeSettings
* @param string $currentScope
*/
public function mapConfig(array &$scopeSettings, $currentScope, ContextualizerInterface $contextualizer): void
{
if (empty($scopeSettings['user_profile'])) {
return;
}

$contextualizer->setContextualParameter(
'user_profile.enabled',
$currentScope,
$scopeSettings['user_profile']['enabled']
);

$contextualizer->setContextualParameter(
'user_profiler.content_types',
$currentScope,
$scopeSettings['user_profile']['content_types']
);

$contextualizer->setContextualParameter(
'user_profile.field_groups',
$currentScope,
$scopeSettings['user_profile']['field_groups']
);
}
}
1 change: 1 addition & 0 deletions src/bundle/IbexaAdminUiBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ private function getConfigParsers(): array
new Parser\LimitationValueTemplates(),
new Parser\Assets(),
new Parser\AdminUiParser(),
new Parser\UserProfile(),
];
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/bundle/Resources/config/ezplatform_default_settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ parameters:
ibexa.site_access.config.default.subitems_module.limit: 10

# User identifier
ibexa.site_access.config.admin_group.user_content_type_identifier: ['user']
ibexa.site_access.config.admin_group.user_content_type_identifier: ['user', 'editor']

# User Group identifier
ibexa.site_access.config.admin_group.user_group_content_type_identifier: ['user_group']
Expand Down Expand Up @@ -82,3 +82,8 @@ parameters:

ibexa.site_access.config.admin_group.autosave.enabled: true
ibexa.site_access.config.admin_group.autosave.interval: 60

# User profiles
ibexa.site_access.config.admin_group.user_profile.enabled: false
ibexa.site_access.config.admin_group.user_profile.content_types: ['editor', 'user']
ibexa.site_access.config.admin_group.user_profile.field_groups: ['about', 'contact']
12 changes: 12 additions & 0 deletions src/bundle/Resources/config/routing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,18 @@ ibexa.user.invite.to_group:
controller: Ibexa\Bundle\AdminUi\Controller\User\InvitationController::sendInvitationsAction
methods: ['POST']

ibexa.user.profile.view:
path: /user/profile/{userId}/view
controller: Ibexa\Bundle\AdminUi\Controller\User\ProfileViewController::viewAction
methods: ['GET']
options:
expose: true

ibexa.user.profile.edit:
path: /user/profile/edit
controller: Ibexa\Bundle\AdminUi\Controller\User\ProfileEditController::editAction
methods: ['GET', 'POST']

#
# Custom URL alias
#
Expand Down
1 change: 1 addition & 0 deletions src/bundle/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ imports:
- { resource: services/form_ui_action_mappers.yaml }
- { resource: services/views.yaml }
- { resource: services/translation.yaml }
- { resource: services/user_profile.yaml }
- { resource: services/user_settings.yaml }
- { resource: services/rest.yaml }
- { resource: services/permissions.yaml }
Expand Down
Loading

0 comments on commit a4f9a3f

Please sign in to comment.