apigee_edge_teams.module in Apigee Edge 8
Copyright 2018 Google Inc.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
File
modules/apigee_edge_teams/apigee_edge_teams.moduleView source
<?php
/**
* @file
* Copyright 2018 Google Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
use Drupal\apigee_edge\Exception\DeveloperDoesNotExistException;
use Drupal\apigee_edge_teams\Entity\TeamInterface;
use Drupal\apigee_edge_teams\Entity\TeamInvitationInterface;
use Drupal\apigee_edge_teams\Entity\TeamRoleInterface;
use Drupal\apigee_edge_teams\EventSubscriber\TeamInactiveStatusSubscriber;
use Drupal\Component\Render\PlainTextOutput;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
/**
* Implements hook_module_implements_alter().
*/
function apigee_edge_teams_module_implements_alter(&$implementations, $hook) {
if (\Drupal::moduleHandler()
->moduleExists('devel') && $hook === 'entity_type_alter') {
// Move apigee_edge_teams_entity_type_alter() to the end of the list if
// Devel module is enabled.
// @see devel_entity_type_alter()
$group = $implementations['apigee_edge_teams'];
unset($implementations['apigee_edge_teams']);
$implementations['apigee_edge_teams'] = $group;
}
}
/**
* Implements hook_entity_type_alter().
*/
function apigee_edge_teams_entity_type_alter(array &$entity_types) {
// Fixes Team App entity routes generated by the Devel module.
// @see devel_entity_type_alter()
// @see \Drupal\apigee_edge_teams\Routing\TeamAppDevelRouteFixerSubscriber
if (\Drupal::moduleHandler()
->moduleExists('devel')) {
/** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
$entity_type =& $entity_types['team_app'];
if ($entity_type
->hasLinkTemplate('canonical')) {
$canonical_link = $entity_type
->getLinkTemplate('canonical');
$entity_type
->setLinkTemplate('devel-render', "{$canonical_link}/devel-render");
$entity_type
->setLinkTemplate('devel-definition', "{$canonical_link}/devel-definition");
if ($entity_type
->hasLinkTemplate('edit-form')) {
$entity_type
->setLinkTemplate('devel-load', "{$canonical_link}/devel-load");
}
}
}
}
/**
* Gets the title of team listing page.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* The title of the page.
*/
function apigee_edge_teams_team_listing_page_title() : TranslatableMarkup {
$args['@teams'] = \Drupal::entityTypeManager()
->getDefinition('team')
->getCollectionLabel();
$title = t('@teams', $args);
// Modules and themes can alter the title.
\Drupal::moduleHandler()
->alter('apigee_edge_teams_team_listing_page_title', $title);
return $title;
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function apigee_edge_teams_form_entity_form_display_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if ($form['#entity_type'] === 'team_app') {
$form['#validate'][] = '_apigee_edge_teams_team_app_entity_form_display_edit_form_validate';
}
}
/**
* Extra validation for the entity_form_display.edit form of team apps.
*
* This makes sure that fields marked as 'required' can't be disabled.
*
* @param array $form
* Form array.
* @param Drupal\Core\Form\FormStateInterface $form_state
* Form state.
*/
function _apigee_edge_teams_team_app_entity_form_display_edit_form_validate(array &$form, FormStateInterface $form_state) {
$required = \Drupal::config('apigee_edge_teams.team_app_settings')
->get('required_base_fields');
foreach ($form_state
->getValue('fields') as $field_name => $data) {
if (in_array($field_name, $required) && $data['region'] === 'hidden') {
$form_state
->setError($form['fields'][$field_name], t('%field-name is required.', [
'%field-name' => $form['fields'][$field_name]['human_name']['#plain_text'],
]));
}
}
}
/**
* Implements hook_ENTITY_TYPE_delete().
*/
function apigee_edge_teams_user_delete(EntityInterface $entity) {
/** @var \Drupal\user\UserInterface $entity */
/** @var \Drupal\apigee_edge_teams\Entity\Storage\TeamMemberRoleStorageInterface $team_member_role_storage */
$team_member_role_storage = \Drupal::entityTypeManager()
->getStorage('team_member_role');
// When a user gets deleted then its developer account also gets deleted
// from Apigee Edge which removes its (team) company memberships.
// We must delete this user's team roles from Drupal as well.
foreach ($team_member_role_storage
->loadByDeveloper($entity) as $team_member_roles_in_team) {
try {
$team_member_roles_in_team
->delete();
} catch (EntityStorageException $e) {
\Drupal::logger('apigee_edge_teams')
->critical("Integrity check: Failed to remove %developer team member's roles in %team team when its Drupal user got deleted.", [
'%developer' => $entity
->getEmail(),
'%team' => $team_member_roles_in_team
->getTeam()
->id(),
]);
}
}
}
/**
* Implements hook_ENTITY_TYPE_delete().
*/
function apigee_edge_teams_developer_delete(EntityInterface $entity) {
/** @var \Drupal\apigee_edge\Entity\DeveloperInterface $entity */
/** @var \Drupal\apigee_edge_teams\CompanyMembershipObjectCacheInterface $cache */
$cache = \Drupal::service('apigee_edge_teams.cache.company_membership_object');
// Remove all company membership object cache entries that contained the
// removed developer.
$cache
->invalidateMemberships([
"developer:{$entity->getEmail()}",
]);
}
/**
* Implements hook_ENTITY_TYPE_delete().
*/
function apigee_edge_teams_team_delete(EntityInterface $entity) {
// Delete all invitations from this team.
/** @var \Drupal\apigee_edge_teams\Entity\Storage\TeamStorageInterface $storage */
$storage = \Drupal::entityTypeManager()
->getStorage('team_invitation');
if ($invitations = $storage
->loadByProperties([
'team' => $entity
->id(),
])) {
$storage
->delete($invitations);
}
}
/**
* Implements hook_system_breadcrumb_alter().
*/
function apigee_edge_teams_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context) {
if ($route_match
->getRouteName() === 'entity.team.add_form') {
$team_entity_def = \Drupal::entityTypeManager()
->getDefinition('team');
$breadcrumb
->addLink(Link::createFromRoute($team_entity_def
->getPluralLabel(), 'entity.team.collection'));
}
elseif ($route_match
->getRouteName() === 'entity.team.add_members') {
/** @var \Drupal\apigee_edge_teams\Entity\TeamInterface $team */
$team = $route_match
->getParameter('team');
$breadcrumb
->addLink($team
->toLink(t('Members'), 'members'));
}
elseif ($route_match
->getRouteName() === 'entity.team_app.add_form_for_team') {
$team_app_entity_def = \Drupal::entityTypeManager()
->getDefinition('team_app');
/** @var \Drupal\apigee_edge_teams\Entity\TeamInterface $team */
$team = $route_match
->getParameter('team');
$breadcrumb
->addLink(Link::createFromRoute($team_app_entity_def
->getPluralLabel(), 'entity.team_app.collection_by_team', [
'team' => $team
->id(),
]));
}
}
/**
* Implements hook_preprocess().
*/
function apigee_edge_teams_preprocess(&$variables, $hook) {
if (!in_array($hook, [
'menu_local_action',
'menu_local_task',
])) {
return;
}
/** @var \Drupal\Core\Url $url */
$url = $variables['link']['#url'];
if (!in_array($url
->getRouteName(), TeamInactiveStatusSubscriber::getDisabledRoutes())) {
return;
}
$team = \Drupal::routeMatch()
->getParameter('team');
if (!$team || $team
->getStatus() !== TeamInterface::STATUS_INACTIVE) {
return;
}
// If a team is inactive, for the local tasks and local actions, set the url
// to <none>.
$variables['link']['#url'] = Url::fromRoute('<none>', [], [
'absolute' => TRUE,
]);
// Add a title attribute.
$variables['link']['#options']['attributes']['title'] = t('This @team is inactive.', [
'@team' => \Drupal::entityTypeManager()
->getDefinition('team')
->getSingularLabel(),
]);
// Add a disabled class.
$variables['link']['#options']['attributes']['class'][] = 'team-disabled-action';
$variables['#attached']['library'][] = 'apigee_edge_teams/disabled_action';
}
/**
* Implements hook_entity_operation_alter().
*/
function apigee_edge_teams_entity_operation_alter(array &$operations, EntityInterface $entity) {
foreach ($operations as $key => $operation) {
if (!in_array($operation['url']
->getRouteName(), TeamInactiveStatusSubscriber::getDisabledRoutes())) {
continue;
}
$team = \Drupal::routeMatch()
->getParameter('team');
if (!$team || $team
->getStatus() !== TeamInterface::STATUS_INACTIVE) {
continue;
}
// Remove the entity operation if the team is inactive.
unset($operations[$key]);
}
}
/**
* Implements hook_ENTITY_TYPE_access().
*
* Grant "view" and "view label" access to team members based on their
* teams' API Product access.
*/
function apigee_edge_teams_api_product_access(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\apigee_edge\Entity\ApiProductInterface $entity */
// The "assign" in not in this list, because it is handled by team API Product
// access manager service directly. Team members should not be able to
// assign API products to their developer apps just because they have access
// to do that when they are creating team app for a team.
if (!in_array($operation, [
'view',
'view label',
])) {
return AccessResult::neutral(sprintf('%s is not supported by %s.', $operation, __FUNCTION__));
}
if ($account
->isAnonymous()) {
return AccessResult::neutral('Anonymous user can not be member of a team.');
}
/** @var \Drupal\apigee_edge_teams\TeamMemberApiProductAccessHandlerInterface $access_checker */
$access_checker = \Drupal::service('apigee_edge_teams.team_member_api_product_access_handler');
/** @var \Drupal\apigee_edge_teams\TeamMembershipManagerInterface $team_membership_manager */
$team_membership_manager = \Drupal::service('apigee_edge_teams.team_membership_manager');
try {
$developer_team_ids = $team_membership_manager
->getTeams($account
->getEmail());
} catch (DeveloperDoesNotExistException $e) {
return AccessResult::neutral($e
->getMessage());
}
if (empty($developer_team_ids)) {
$result = AccessResult::neutral("{$account->getEmail()} is not member of any team.");
// If developer's team membership changes access must be re-evaluated.
// @see \Drupal\apigee_edge_teams\TeamMembershipManager
/** @var \Drupal\apigee_edge\Entity\Storage\DeveloperStorageInterface $developer_storage */
$developer_storage = \Drupal::entityTypeManager()
->getStorage('developer');
$developer = $developer_storage
->load($account
->getEmail());
if ($developer) {
$result
->addCacheableDependency($developer);
}
}
else {
/** @var \Drupal\apigee_edge_teams\Entity\Storage\TeamStorageInterface $team_storage */
$team_storage = \Drupal::entityTypeManager()
->getStorage('team');
/** @var \Drupal\apigee_edge_teams\Entity\TeamInterface $team */
$teams = $team_storage
->loadMultiple($developer_team_ids);
foreach ($teams as $team) {
$result = $access_checker
->access($entity, $operation, $team, $account, TRUE);
if ($result
->isAllowed()) {
break;
}
}
}
// $result is always defined.
return $result;
}
/**
* Implements hook_help().
*/
function apigee_edge_teams_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'entity.team_role.collection':
$member_role = \Drupal::entityTypeManager()
->getStorage('team_role')
->load(TeamRoleInterface::TEAM_MEMBER_ROLE);
return '<p>' . t('A role defines a group of users that have certain privileges. These privileges are defined on the <a href=":permissions">Permissions page</a>. Here you can define the names of the team roles on your site. Users who are part of a team have the :member team role, plus any other team roles granted to their user account.', [
':permissions' => Url::fromRoute('apigee_edge_teams.settings.team.permissions')
->toString(),
':member' => $member_role
->label(),
]) . '</p>';
}
}
/**
* Implements hook_mail().
*/
function apigee_edge_teams_mail($key, &$message, $params) {
$token_service = \Drupal::token();
$config = \Drupal::config('apigee_edge_teams.team_settings');
switch ($key) {
case 'team_invitation_created':
$template = empty($params['user']) ? 'new' : 'existing';
$message['subject'] = PlainTextOutput::renderFromHtml($token_service
->replace($config
->get("team_invitation_email_{$template}.subject"), $params));
$message['body'][] = $token_service
->replace($config
->get("team_invitation_email_{$template}.body"), $params);
break;
}
}
/**
* Implements hook_cron().
*/
function apigee_edge_teams_cron() {
/** @var \Drupal\apigee_edge_teams\Entity\Storage\TeamInvitationStorageInterface $storage */
$storage = Drupal::entityTypeManager()
->getStorage('team_invitation');
$team_invitations = $storage
->getInvitationsToExpire();
if (!count($team_invitations)) {
return;
}
// Update status for expired invitations.
foreach ($team_invitations as $team_invitation) {
$team_invitation
->setStatus(TeamInvitationInterface::STATUS_EXPIRED)
->save();
}
}