Skip to content

Commit 932364e

Browse files
authored
[#508] Disable team actions if team is inactive (#518)
* [#508] Implement team inactive routes * [#508] Update deprecated message * [#508] Check for inactive team * [#508] Add module_handler
1 parent 5883a11 commit 932364e

7 files changed

+280
-4
lines changed

modules/apigee_edge_teams/apigee_edge_teams.libraries.yml

+6
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ switcher:
1616
css:
1717
theme:
1818
css/apigee_edge_teams.switcher.css: {}
19+
20+
disabled_action:
21+
version: VERSION
22+
css:
23+
theme:
24+
css/apigee_edge_teams.disabled_action.css: {}

modules/apigee_edge_teams/apigee_edge_teams.module

+54
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
*/
2121

2222
use Drupal\apigee_edge\Exception\DeveloperDoesNotExistException;
23+
use Drupal\apigee_edge_teams\Entity\TeamInterface;
2324
use Drupal\apigee_edge_teams\Entity\TeamInvitationInterface;
2425
use Drupal\apigee_edge_teams\Entity\TeamRoleInterface;
26+
use Drupal\apigee_edge_teams\EventSubscriber\TeamInactiveStatusSubscriber;
2527
use Drupal\Component\Render\PlainTextOutput;
2628
use Drupal\Core\Access\AccessResult;
2729
use Drupal\Core\Breadcrumb\Breadcrumb;
@@ -171,6 +173,58 @@ function apigee_edge_teams_system_breadcrumb_alter(Breadcrumb &$breadcrumb, Rout
171173
}
172174
}
173175

176+
/**
177+
* Implements hook_preprocess().
178+
*/
179+
function apigee_edge_teams_preprocess(&$variables, $hook) {
180+
if (!in_array($hook, ['menu_local_action', 'menu_local_task'])) {
181+
return;
182+
}
183+
184+
/** @var \Drupal\Core\Url $url */
185+
$url = $variables['link']['#url'];
186+
if (!in_array($url->getRouteName(), TeamInactiveStatusSubscriber::getDisabledRoutes())) {
187+
return;
188+
}
189+
190+
$team = \Drupal::routeMatch()->getParameter('team');
191+
if (!$team || $team->getStatus() !== TeamInterface::STATUS_INACTIVE) {
192+
return;
193+
}
194+
195+
// If a team is inactive, for the local tasks and local actions, set the url
196+
// to <none>.
197+
$variables['link']['#url'] = Url::fromRoute('<none>', [], [
198+
'absolute' => TRUE,
199+
]);
200+
// Add a title attribute.
201+
$variables['link']['#options']['attributes']['title'] = t('This @team is inactive.', [
202+
'@team' => \Drupal::entityTypeManager()->getDefinition('team')->getSingularLabel(),
203+
]);
204+
// Add a disabled class.
205+
$variables['link']['#options']['attributes']['class'][] = 'team-disabled-action';
206+
$variables['#attached']['library'][] = 'apigee_edge_teams/disabled_action';
207+
}
208+
209+
/**
210+
* Implements hook_entity_operation_alter().
211+
*/
212+
function apigee_edge_teams_entity_operation_alter(array &$operations, EntityInterface $entity) {
213+
foreach ($operations as $key => $operation) {
214+
if (!in_array($operation['url']->getRouteName(), TeamInactiveStatusSubscriber::getDisabledRoutes())) {
215+
continue;
216+
}
217+
218+
$team = \Drupal::routeMatch()->getParameter('team');
219+
if (!$team || $team->getStatus() !== TeamInterface::STATUS_INACTIVE) {
220+
continue;
221+
}
222+
223+
// Remove the entity operation if the team is inactive.
224+
unset($operations[$key]);
225+
}
226+
}
227+
174228
/**
175229
* Implements hook_ENTITY_TYPE_access().
176230
*

modules/apigee_edge_teams/apigee_edge_teams.services.yml

+10
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ services:
9393
class: Drupal\apigee_edge_teams\TeamInvitationNotifierEmail
9494
arguments: ['@plugin.manager.mail', '@language_manager']
9595

96+
apigee_edge_teams.team_inactive_status_subscriber:
97+
class: Drupal\apigee_edge_teams\EventSubscriber\TeamInactiveStatusSubscriber
98+
arguments:
99+
- '@class_resolver'
100+
- '@current_route_match'
101+
- '%main_content_renderers%'
102+
- '@current_user'
103+
tags:
104+
- { name: event_subscriber }
105+
96106
route.subscriber.apigee_edge_teams.team_app_by_name:
97107
class: Drupal\apigee_edge_teams\Routing\TeamAppByNameRouteAlterSubscriber
98108
tags:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2020 Google Inc.
3+
*
4+
* This program is free software; you can redistribute it and/or
5+
* modify it under the terms of the GNU General Public License
6+
* version 2 as published by the Free Software Foundation.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program; if not, write to the Free Software
15+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16+
* MA 02110-1301, USA.
17+
*/
18+
19+
.team-disabled-action {
20+
cursor: not-allowed !important;
21+
opacity: 0.5;
22+
}

modules/apigee_edge_teams/src/Controller/TeamMembersList.php

+23-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Drupal\Component\Utility\Html;
2828
use Drupal\Core\Controller\ControllerBase;
2929
use Drupal\Core\Entity\EntityTypeManagerInterface;
30+
use Drupal\Core\Extension\ModuleHandlerInterface;
3031
use Drupal\Core\Url;
3132
use Drupal\user\UserInterface;
3233
use Drupal\views\Views;
@@ -51,21 +52,37 @@ class TeamMembersList extends ControllerBase {
5152
*/
5253
protected $defaultRoles = [];
5354

55+
/**
56+
* The module handler.
57+
*
58+
* @var \Drupal\Core\Extension\ModuleHandlerInterface|null
59+
*/
60+
protected $moduleHandler;
61+
5462
/**
5563
* TeamMembersList constructor.
5664
*
5765
* @param \Drupal\apigee_edge_teams\TeamMembershipManagerInterface $team_membership_manager
5866
* The team membership manager service.
5967
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
6068
* The entity type manager.
69+
* @param \Drupal\Core\Extension\ModuleHandlerInterface|null $module_handler
70+
* The module handler.
6171
*/
62-
public function __construct(TeamMembershipManagerInterface $team_membership_manager, EntityTypeManagerInterface $entity_type_manager) {
72+
public function __construct(TeamMembershipManagerInterface $team_membership_manager, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler = NULL) {
73+
if (!$module_handler) {
74+
@trigger_error('Calling ' . __METHOD__ . ' without the $module_handler is deprecated in apigee_edge:8-x-1.19 and is required before apigee_edge:8.x-2.0. See https://github.com/apigee/apigee-edge-drupal/pull/518.', E_USER_DEPRECATED);
75+
$module_handler = \Drupal::moduleHandler();
76+
}
77+
6378
$this->teamMembershipManager = $team_membership_manager;
6479
$this->entityTypeManager = $entity_type_manager;
6580

6681
if ($role = $this->entityTypeManager()->getStorage('team_role')->load(TeamRoleInterface::TEAM_MEMBER_ROLE)) {
6782
$this->defaultRoles = [$role->id() => $role->label()];
6883
}
84+
85+
$this->moduleHandler = $module_handler;
6986
}
7087

7188
/**
@@ -74,7 +91,8 @@ public function __construct(TeamMembershipManagerInterface $team_membership_mana
7491
public static function create(ContainerInterface $container) {
7592
return new static(
7693
$container->get('apigee_edge_teams.team_membership_manager'),
77-
$container->get('entity_type.manager')
94+
$container->get('entity_type.manager'),
95+
$container->get('module_handler')
7896
);
7997
}
8098

@@ -253,6 +271,9 @@ protected function getOperations(string $member, TeamInterface $team) {
253271
];
254272
}
255273

274+
// Allow modules to alter operations.
275+
$this->moduleHandler->alter('entity_operation', $operations, $team);
276+
256277
return $operations;
257278
}
258279

modules/apigee_edge_teams/src/Entity/ListBuilder/TeamInvitationListBuilder.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ class TeamInvitationListBuilder extends EntityListBuilder {
3131
/**
3232
* {@inheritdoc}
3333
*/
34-
public function getOperations(EntityInterface $entity) {
34+
public function getDefaultOperations(EntityInterface $entity) {
3535
/** @var \Drupal\apigee_edge_teams\Entity\TeamInvitationInterface $entity */
36-
$operations = [];
36+
$operations = parent::getDefaultOperations($entity);
3737

3838
if ($entity->isPending() && $entity->access('accept') && $entity->hasLinkTemplate('accept-form')) {
3939
$operations['accept'] = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
/**
4+
* Copyright 2020 Google Inc.
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU General Public License
8+
* version 2 as published by the Free Software Foundation.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18+
* MA 02110-1301, USA.
19+
*/
20+
21+
namespace Drupal\apigee_edge_teams\EventSubscriber;
22+
23+
use Drupal\apigee_edge_teams\Entity\TeamInterface;
24+
use Drupal\Core\DependencyInjection\ClassResolverInterface;
25+
use Drupal\Core\Routing\RouteMatchInterface;
26+
use Drupal\Core\Session\AccountInterface;
27+
use Drupal\Core\StringTranslation\StringTranslationTrait;
28+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
29+
use Symfony\Component\HttpFoundation\Response;
30+
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
31+
use Symfony\Component\HttpKernel\KernelEvents;
32+
33+
/**
34+
* Displays an error message on team pages if team is inactive.
35+
*/
36+
class TeamInactiveStatusSubscriber implements EventSubscriberInterface {
37+
38+
use StringTranslationTrait;
39+
40+
/**
41+
* The class resolver service.
42+
*
43+
* @var \Drupal\Core\Controller\ControllerResolverInterface
44+
*/
45+
protected $classResolver;
46+
47+
/**
48+
* The route match service.
49+
*
50+
* @var \Drupal\Core\Routing\RouteMatchInterface
51+
*/
52+
protected $routeMatch;
53+
54+
/**
55+
* The available main content renderer services, keyed per format.
56+
*
57+
* @var array
58+
*/
59+
protected $mainContentRenderers;
60+
61+
/**
62+
* The current user.
63+
*
64+
* @var \Drupal\Core\Session\AccountInterface
65+
*/
66+
protected $currentUser;
67+
68+
/**
69+
* TeamInactiveStatusSubscriber constructor.
70+
*
71+
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
72+
* The class resolver service.
73+
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
74+
* The route match service.
75+
* @param array $main_content_renderers
76+
* The available main content renderer service IDs.
77+
* @param \Drupal\Core\Session\AccountInterface $current_user
78+
* The current user.
79+
*/
80+
public function __construct(ClassResolverInterface $class_resolver, RouteMatchInterface $route_match, array $main_content_renderers, AccountInterface $current_user) {
81+
$this->classResolver = $class_resolver;
82+
$this->routeMatch = $route_match;
83+
$this->mainContentRenderers = $main_content_renderers;
84+
$this->currentUser = $current_user;
85+
}
86+
87+
/**
88+
* Display an error message on inactive team routes.
89+
*
90+
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
91+
* The event to process.
92+
*/
93+
public function onRespond(FilterResponseEvent $event) {
94+
if ($this->currentUser->isAnonymous() || !in_array($this->routeMatch->getRouteName(), $this->getDisabledRoutes())) {
95+
return;
96+
}
97+
98+
/** @var \Drupal\apigee_edge_teams\Entity\TeamInterface $team */
99+
$team = $this->routeMatch->getParameter('team');
100+
if (!$team || $team->getStatus() !== TeamInterface::STATUS_INACTIVE) {
101+
return;
102+
}
103+
104+
$content = [
105+
'content' => [
106+
'#theme' => 'status_messages',
107+
'#message_list' => [
108+
'error' => [
109+
$this->t('The %team_name @team is inactive. This operation is now allowed.', [
110+
'%team_name' => $team->label(),
111+
'@team' => $team->getEntityType()->getSingularLabel(),
112+
]),
113+
],
114+
],
115+
],
116+
];
117+
118+
$renderer = $this->classResolver->getInstanceFromDefinition($this->mainContentRenderers['html']);
119+
/* @var \Symfony\Component\HttpFoundation\Response $response */
120+
$response = $renderer->renderResponse($content, $event->getRequest(), $this->routeMatch);
121+
$response->setStatusCode(Response::HTTP_FORBIDDEN);
122+
123+
$event->setResponse($response);
124+
}
125+
126+
/**
127+
* {@inheritdoc}
128+
*/
129+
public static function getSubscribedEvents() {
130+
$events[KernelEvents::RESPONSE][] = ['onRespond', 5];
131+
return $events;
132+
}
133+
134+
/**
135+
* Returns an array of route names for which the warning message should be displayed.
136+
*
137+
* @return array
138+
* An array of route names.
139+
*/
140+
public static function getDisabledRoutes(): array {
141+
return [
142+
// Team.
143+
'entity.team.edit_form',
144+
'entity.team.delete_form',
145+
146+
// Team app.
147+
'entity.team_app.add_form_for_team',
148+
'entity.team_app.edit_form',
149+
'entity.team_app.delete_form',
150+
'entity.team_app.analytics',
151+
152+
// Team member.
153+
'entity.team.add_members',
154+
'entity.team.member.edit',
155+
'entity.team.member.remove',
156+
157+
// Team invitation.
158+
'entity.team_invitation.resend_form',
159+
'entity.team_invitation.delete_form',
160+
];
161+
}
162+
163+
}

0 commit comments

Comments
 (0)