apigee_m10n-8.x-1.7/modules/apigee_m10n_teams/src/MonetizationTeams.php
modules/apigee_m10n_teams/src/MonetizationTeams.php
<?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. */ namespace Drupal\apigee_m10n_teams; use Apigee\Edge\Api\Monetization\Structure\LegalEntityTermsAndConditionsHistoryItem; use Drupal\Core\Access\AccessResult; use Drupal\Core\Access\AccessResultInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Drupal\apigee_edge\Entity\ApiProductInterface; use Drupal\apigee_edge_teams\Entity\TeamInterface; use Drupal\apigee_m10n\Exception\SdkEntityLoadException; use Drupal\apigee_m10n\MonetizationInterface; use Drupal\apigee_m10n_teams\Access\TeamPermissionAccessInterface; use Drupal\apigee_m10n_teams\Entity\Access\TeamRatePlanAccessControlHandler; use Drupal\apigee_m10n_teams\Entity\Access\TeamRatePlanSubscriptionAccessHandler; use Drupal\apigee_m10n_teams\Entity\Form\TeamPurchasedPlanForm; use Drupal\apigee_m10n_teams\Entity\Routing\MonetizationTeamsEntityRouteProvider; use Drupal\apigee_m10n_teams\Entity\Storage\TeamProductBundleStorage; use Drupal\apigee_m10n_teams\Entity\Storage\TeamPurchasedPlanStorage; use Drupal\apigee_m10n_teams\Entity\TeamProductBundle; use Drupal\apigee_m10n_teams\Entity\TeamsPurchasedPlan; use Drupal\apigee_m10n_teams\Entity\TeamsRatePlan; use Drupal\apigee_m10n_teams\Plugin\Field\FieldFormatter\TeamPurchasePlanFormFormatter; use Drupal\apigee_m10n_teams\Plugin\Field\FieldFormatter\TeamPurchasePlanLinkFormatter; use Drupal\apigee_m10n_teams\Plugin\Field\FieldWidget\CompanyTermsAndConditionsWidget; use Psr\Log\LoggerInterface; /** * The `apigee_m10n.teams` service. */ class MonetizationTeams implements MonetizationTeamsInterface { /** * The current route match. * * @var \Drupal\Core\Routing\RouteMatchInterface */ protected $route_match; /** * The `apigee_m10n.monetization` service. * * @var \Drupal\apigee_m10n\MonetizationInterface */ protected $monetization; /** * The Teams SDK controller factory. * * @var \Drupal\apigee_m10n\ */ protected $sdk_controller_factory; /** * Static cache of `acceptLatestTermsAndConditions` results. * * @var array */ protected $companyAcceptedTermsStatus; /** * The logger. * * @var \Psr\Log\LoggerInterface */ protected $logger; /** * MonetizationTeams constructor. * * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The current route match. * @param \Drupal\apigee_m10n_teams\TeamSdkControllerFactoryInterface $sdk_controller_factory * The SDK controller factory. * @param \Drupal\apigee_m10n\MonetizationInterface $monetization * The monetization service. * @param \Psr\Log\LoggerInterface $logger * The logger. */ public function __construct(RouteMatchInterface $route_match, TeamSdkControllerFactoryInterface $sdk_controller_factory, MonetizationInterface $monetization, LoggerInterface $logger) { $this->route_match = $route_match; $this->sdk_controller_factory = $sdk_controller_factory; $this->monetization = $monetization; $this->logger = $logger; } /** * {@inheritdoc} */ public function entityTypeAlter(array &$entity_types) { /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */ if (isset($entity_types['product_bundle'])) { // Use our class to override the original entity class. $entity_types['product_bundle']->setClass(TeamProductBundle::class); // Create a link template for team product bundles. $entity_types['product_bundle']->setLinkTemplate('team', '/teams/{team}/monetization/product-bundle/{product_bundle}'); // Get the entity route providers. $route_providers = $entity_types['product_bundle']->getRouteProviderClasses(); // Override the `html` route provider. $route_providers['html'] = MonetizationTeamsEntityRouteProvider::class; $entity_types['product_bundle']->setHandlerClass('route_provider', $route_providers); // Override the storage class. $entity_types['product_bundle']->setStorageClass(TeamProductBundleStorage::class); } // Overrides for the `rate_plan` entity. if (isset($entity_types['rate_plan'])) { // Use our class to override the original entity class. $entity_types['rate_plan']->setClass(TeamsRatePlan::class); $entity_types['rate_plan']->setLinkTemplate('team', '/teams/{team}/monetization/product-bundle/{product_bundle}/plan/{rate_plan}'); $entity_types['rate_plan']->setLinkTemplate('team-purchase', '/teams/{team}/monetization/product-bundle/{product_bundle}/plan/{rate_plan}/purchase'); // Get the entity route providers. $route_providers = $entity_types['rate_plan']->getRouteProviderClasses(); // Override the `html` route provider. $route_providers['html'] = MonetizationTeamsEntityRouteProvider::class; $entity_types['rate_plan']->setHandlerClass('route_provider', $route_providers); $entity_types['rate_plan']->setHandlerClass('access', TeamRatePlanAccessControlHandler::class); $entity_types['rate_plan']->setHandlerClass('subscription_access', TeamRatePlanSubscriptionAccessHandler::class); } // Overrides for the purchased_plan entity. if (isset($entity_types['purchased_plan'])) { // Use our class to override the original entity class. $entity_types['purchased_plan']->setClass(TeamsPurchasedPlan::class); // Override the storage class. $entity_types['purchased_plan']->setStorageClass(TeamPurchasedPlanStorage::class); // Override purchase form. $entity_types['purchased_plan']->setFormClass('default', TeamPurchasedPlanForm::class); // Create a link template for team purchased plan collection. $entity_types['purchased_plan']->setLinkTemplate('team_collection', '/teams/{team}/monetization/purchased-plans'); } } /** * {@inheritdoc} */ public function fieldFormatterInfoAlter(array &$info) { // Override the purchase link and form formatters. $info['apigee_purchase_plan_form']['class'] = TeamPurchasePlanFormFormatter::class; $info['apigee_purchase_plan_link']['class'] = TeamPurchasePlanLinkFormatter::class; } /** * {@inheritdoc} */ public function fieldWidgetInfoAlter(array &$info) { // Override the terms and condition widget. $info['apigee_tnc_widget']['class'] = CompanyTermsAndConditionsWidget::class; } /** * {@inheritdoc} */ public function purchasedPlanAccess(EntityInterface $entity, $operation, AccountInterface $account) { /** @var \Drupal\apigee_m10n_teams\Entity\TeamsPurchasedPlanInterface $entity */ if ($entity->isTeamPurchasedPlan() && ($team = $entity->getTeamEntity())) { // Gat the access result. $access = $this->teamAccessCheck()->allowedIfHasTeamPermissions($team, $account, ["{$operation} purchased_plan"]); // Team permission results completely override user permissions. return $access->isAllowed() ? $access : AccessResult::forbidden($access->getReason()); } } /** * {@inheritdoc} */ public function isTeamAlreadySubscribed(string $team_id, TeamsRatePlan $rate_plan): bool { // Use cached result if available. // See: \Drupal\apigee_m10n_teams\Entity\Storage\TeamPurchasedPlanStorage::loadByTeamId() $cid = "apigee_m10n_teams:dev:team_purchased_plans:{$team_id}"; if ($cache = \Drupal::cache()->get($cid)) { $teamPurchases = $cache->data; } else { $teamPurchases = TeamsPurchasedPlan::loadByTeamId($team_id); \Drupal::cache()->set($cid, $teamPurchases, strtotime('now + 5 minutes')); } foreach ($teamPurchases as $team_purchased_plan) { if ($team_purchased_plan->getRatePlan()->id() == $rate_plan->id() && $team_purchased_plan->isActive()) { return TRUE; } } return FALSE; } /** * {@inheritdoc} */ public function entityAccess(EntityInterface $entity, $operation, AccountInterface $account) { if ($team = $this->currentTeam()) { // Get the access result. $access = $this->teamAccessCheck()->allowedIfHasTeamPermissions($team, $account, ["{$operation} {$entity->getEntityTypeId()}"]); // Team permission results completely override user permissions. return $access->isAllowed() ? $access : AccessResult::forbidden($access->getReason()); } } /** * {@inheritdoc} */ public function currentTeam(): ?TeamInterface { // @todo This call could be much smarter. // All team routes have the team as the first parameter and we could be // checking a route list to make sure the team is part of a team route // similar to the `_apigee_monetization_route` route option. return $this->route_match->getParameter('team'); } /** * Helper that gets the `TeamPermissionAccessCheck` service. * * This would be injected but injection causes a circular reference error when * rebuilding the container due to it's dependency on the * `apigee_edge_teams.team_permissions` service. * * See: <https://github.com/apigee/apigee-edge-drupal/pull/138#discussion_r259570088>. * * @return \Drupal\apigee_m10n_teams\Access\TeamPermissionAccessInterface * The team permission access checker. */ protected function teamAccessCheck(): TeamPermissionAccessInterface { return \Drupal::service('apigee_m10n_teams.access_check.team_permission'); } /** * {@inheritdoc} */ public function isLatestTermsAndConditionAccepted(string $company_id): ?bool { if (!($latest_tnc = $this->monetization->getLatestTermsAndConditions())) { // If there isn't a latest TnC, and there was no error, there shouldn't be // anything to accept. // @todo Add a test for an org with no TnC defined. return TRUE; } // Check the cache table. if (!isset($this->companyAcceptedTermsStatus[$company_id])) { // Get the latest TnC ID. $latest_tnc_id = $latest_tnc->id(); // Creates a controller for getting accepted TnC. $controller = $this->sdk_controller_factory->companyTermsAndConditionsController($company_id); try { $history = $controller->getTermsAndConditionsHistory(); } catch (\Exception $e) { $message = "Unable to load Terms and Conditions history for a team \n\n" . $e; $this->logger->error($message); throw new SdkEntityLoadException($message); } // All we care about is the latest entry for the latest TnC. $latest = array_reduce($history, function ($carry, $item) use ($latest_tnc_id) { /** @var \Apigee\Edge\Api\Monetization\Structure\LegalEntityTermsAndConditionsHistoryItem $item */ // No need to look at items other than for the current TnC. if ($item->getTnc()->id() !== $latest_tnc_id) { return $carry; } // Gets the time of the carry over item. $carry_time = $carry instanceof LegalEntityTermsAndConditionsHistoryItem ? $carry->getAuditDate()->getTimestamp() : NULL; return $item->getAuditDate()->getTimestamp() > $carry_time ? $item : $carry; }); $this->companyAcceptedTermsStatus[$company_id] = ($latest instanceof LegalEntityTermsAndConditionsHistoryItem) && $latest->getAction() === 'ACCEPTED'; } return $this->companyAcceptedTermsStatus[$company_id]; } /** * {@inheritdoc} */ public function acceptLatestTermsAndConditions(string $company_id): ?LegalEntityTermsAndConditionsHistoryItem { try { // Reset the static cache for this team. unset($this->companyAcceptedTermsStatus[$company_id]); return $this->sdk_controller_factory->companyTermsAndConditionsController($company_id) ->acceptTermsAndConditionsById($this->monetization->getLatestTermsAndConditions()->id()); } catch (\Throwable $t) { $this->logger->error('Unable to accept latest TnC: ' . $t->getMessage()); } return NULL; } /** * {@inheritdoc} */ public function apiProductTeamAssignmentAccess(ApiProductInterface $api_product, TeamInterface $team, AccountInterface $account): AccessResultInterface { // Cache results for this request. static $eligible_product_cache = []; $company_id = $team->getDisplayName(); if (!isset($eligible_product_cache[$company_id])) { // Instantiate an instance of the m10n ApiProduct controller. $product_controller = $this->sdk_controller_factory->companyApiProductController($company_id); // Get a list of available products for the m10n company. $eligible_product_cache[$company_id] = $product_controller->getEligibleProductsByCompany($company_id); } // Get just the IDs from the available products. $product_ids = array_map(function ($product) { return $product->id(); }, $eligible_product_cache[$company_id]); // Allow only if the id is in the eligible list. return in_array(strtolower($api_product->id()), $product_ids) ? AccessResult::allowed() : AccessResult::forbidden('Product is not eligible for this company'); } }