diff --git a/apigee_m10n.install b/apigee_m10n.install index 1d4de050..33827af0 100644 --- a/apigee_m10n.install +++ b/apigee_m10n.install @@ -150,3 +150,26 @@ function apigee_m10n_update_8202(&$sandbox) { // Clear all caches. drupal_flush_all_caches(); } + +/** + * Updating apiProduct field display in xrate-plan default display mode and enabling 'view + * xproduct' permission for authenticated user. + */ +function apigee_m10n_update_8203(&$sandbox) { + + $display_repository = \Drupal::service('entity_display.repository'); + // Get the default view display for xrateplan. + $view_display = $display_repository->getViewDisplay('xrate_plan', 'xrate_plan', 'default'); + $component = $view_display->getComponent('apiProduct'); + $settings = [ + 'link' => false, + ]; + $view_display->setComponent('apiProduct', ['settings' => $settings,] + $component)->save(); + + if (Drupal::moduleHandler()->moduleExists('user')) { + user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, ['view xproduct']); + } + + // Clear all caches. + drupal_flush_all_caches(); +} diff --git a/config/install/core.entity_view_display.xrate_plan.xrate_plan.default.yml b/config/install/core.entity_view_display.xrate_plan.xrate_plan.default.yml index 5591d162..0199a592 100755 --- a/config/install/core.entity_view_display.xrate_plan.xrate_plan.default.yml +++ b/config/install/core.entity_view_display.xrate_plan.xrate_plan.default.yml @@ -15,7 +15,7 @@ content: region: content label: inline settings: - link: true + link: false third_party_settings: { } displayName: type: string diff --git a/src/Controller/BuyApiController.php b/src/Controller/BuyApiController.php index 19ef2bf1..a0c90839 100755 --- a/src/Controller/BuyApiController.php +++ b/src/Controller/BuyApiController.php @@ -22,6 +22,7 @@ use Apigee\Edge\Api\ApigeeX\Controller\RatePlanControllerInterface; use Drupal\apigee_m10n\ApigeeSdkControllerFactoryInterface; use Drupal\apigee_m10n\Entity\XProduct; +use Drupal\apigee_m10n\Entity\XRatePlan; use Drupal\apigee_m10n\Form\RatePlanXConfigForm; use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Access\AccessResult; @@ -104,17 +105,13 @@ public function xcatalogPage(UserInterface $user) { $rate_plans = []; $subscription_handler = \Drupal::entityTypeManager()->getHandler('xrate_plan', 'subscription_access'); - // Load rate plans for each xproduct. - foreach (XProduct::getAvailablexProductByDeveloper($user->getEmail()) as $xproduct) { - /** @var \Drupal\apigee_m10n\Entity\XProductInterface $xproduct */ - foreach ($xproduct->get('ratePlans') as $rate_plan) { - - /** @var \Drupal\apigee_m10n\Entity\XRatePlanInterface $rate_plan_entity */ - $rate_plan_entity = $rate_plan->entity; - if ($subscription_handler->access($rate_plan_entity, $user) == AccessResult::allowed()) { - $rate_plans["{$xproduct->id()}:{$rate_plan->target_id}"] = $rate_plan->entity; - } - }; + // Get the active rate plans of the organization. + $all_ratePlans = XRatePlan::loadAll(); + + foreach ($all_ratePlans as $rate_plan) { + if ($subscription_handler->access($rate_plan, $user) == AccessResult::allowed()) { + $rate_plans["{$rate_plan->id()}"] = $rate_plan; + } } return $this->buildPage($rate_plans); diff --git a/src/Entity/ParamConverter/XRatePlanConverter.php b/src/Entity/ParamConverter/XRatePlanConverter.php index b5302fd9..bcd2e3da 100755 --- a/src/Entity/ParamConverter/XRatePlanConverter.php +++ b/src/Entity/ParamConverter/XRatePlanConverter.php @@ -53,6 +53,7 @@ public function convert($value, $definition, $name, array $defaults) { $storage = $this->entityTypeManager->getStorage('xrate_plan'); // The rate plan value should already be validated so just load it. $entity = $storage->loadById($xproduct_id, $value); + $this->entityTypeManager->getStorage('xproduct')->loadProducts($xproduct_id); } catch (EntityStorageException $ex) { throw new ParamNotConvertedException('Unable to load rate plan X.', 404, $ex); diff --git a/src/Entity/Storage/Controller/XRatePlanSdkControllerProxy.php b/src/Entity/Storage/Controller/XRatePlanSdkControllerProxy.php index dc844b06..c96484d5 100755 --- a/src/Entity/Storage/Controller/XRatePlanSdkControllerProxy.php +++ b/src/Entity/Storage/Controller/XRatePlanSdkControllerProxy.php @@ -144,4 +144,23 @@ protected function getRatePlanControllerByProductId($product_bundle_id) { return $controller_cache[$product_bundle_id]; } + /** + * {@inheritdoc} + */ + public function loadAllRatePlans(): array { + // Call xproduct when rate plan is called. + XProduct::loadAll(); + /** @var \Apigee\Edge\Api\ApigeeX\Entity\RatePlanInterface[] $rate_plans */ + $rate_plans = []; + + // Get all rate plans for the organization. + $plans = $this->loadRatePlansByProduct('-'); + foreach ($plans as $plan) { + // Key rate plans by their ID. + $rate_plans[$plan->id()] = $plan; + } + + return $rate_plans; + } + } diff --git a/src/Entity/Storage/Controller/XRatePlanSdkControllerProxyInterface.php b/src/Entity/Storage/Controller/XRatePlanSdkControllerProxyInterface.php index 15750d90..fc9111df 100755 --- a/src/Entity/Storage/Controller/XRatePlanSdkControllerProxyInterface.php +++ b/src/Entity/Storage/Controller/XRatePlanSdkControllerProxyInterface.php @@ -55,4 +55,12 @@ public function loadRatePlansByProduct($product_bundle_id, $include_future_plans */ public function loadById(string $product_bundle_id, string $id): EntityInterface; + /** + * Loads all rate plans. + * + * @return array|null + * A list of rate plans keyed by ID. + */ + public function loadAllRatePlans(): array; + } diff --git a/src/Entity/Storage/XProductStorage.php b/src/Entity/Storage/XProductStorage.php index 12448f35..c3cb17be 100755 --- a/src/Entity/Storage/XProductStorage.php +++ b/src/Entity/Storage/XProductStorage.php @@ -163,4 +163,20 @@ public function loadAll() { return $entities; } + /** + * {@inheritdoc} + */ + public function loadProducts($id) { + $entities = []; + $ids = [$id]; + $xproduct = $this->getFromPersistentCache($ids); + // Return the cached entity. + if (isset($xproduct[$id])) { + return $xproduct[$id]; + } + + return $this->loadAll(); + + } + } diff --git a/src/Entity/Storage/XRatePlanStorage.php b/src/Entity/Storage/XRatePlanStorage.php index a9ff5b6e..9a955119 100755 --- a/src/Entity/Storage/XRatePlanStorage.php +++ b/src/Entity/Storage/XRatePlanStorage.php @@ -203,4 +203,37 @@ public function entityController(): EdgeEntityControllerInterface { return $this->controller_proxy; } + /** + * {@inheritdoc} + */ + public function loadAll(): array { + $cid = 'values:xrateplan'; + $rate_plans = $this->cacheBackend->get($cid); + if ($rate_plans && $rate_plans->data) { + return $rate_plans->data; + } + $entities = []; + $this->withController(function (XRatePlanSdkControllerProxyInterface $controller) use (&$entities, $cid) { + $sdk_entities = $controller->loadAllRatePlans(); + + foreach ($sdk_entities as $id => $entity) { + try { + // Filter out rate plans with invalid Ids. + if ($this->isValidId($entity->id())) { + $drupal_entity = $this->createNewInstance($entity); + $entities[$drupal_entity->id()] = $drupal_entity; + } + } + catch (InvalidRatePlanIdException $exception) { + watchdog_exception('apigee_m10n', $exception); + $this->cacheBackend->delete($cid); + } + } + // Cache the entity for 5 mins. + $this->cacheBackend->set($cid, $entities, time() + 299); + }); + + return $entities; + } + } diff --git a/src/Entity/Storage/XRatePlanStorageInterface.php b/src/Entity/Storage/XRatePlanStorageInterface.php index c091c52a..ccc4b93d 100755 --- a/src/Entity/Storage/XRatePlanStorageInterface.php +++ b/src/Entity/Storage/XRatePlanStorageInterface.php @@ -77,4 +77,12 @@ public function loadFutureRatePlan(XRatePlanInterface $ratePlan): ?XRatePlanInte */ public function isValidId(string $id): bool; + /** + * Loads all the rate plans. + * + * @return array|null + * The rate plan. + */ + public function loadAll(): array; + } diff --git a/src/Entity/XRatePlan.php b/src/Entity/XRatePlan.php index 8afdffab..af915c51 100755 --- a/src/Entity/XRatePlan.php +++ b/src/Entity/XRatePlan.php @@ -203,7 +203,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { // The API products are many-to-one. $definitions['apiProduct']->setCardinality(1) - ->setSetting('target_type', 'api_product') + ->setSetting('target_type', 'xproduct') ->setLabel(t('Product')) ->setDescription(t('The API product X the rate plan belongs to.')); @@ -576,4 +576,13 @@ public function getendTimeFormat() { return $endOn ?? "Never"; } + /** + * {@inheritdoc} + */ + public static function loadAll(): array { + return XRatePlan::filterActiveRatePlans(\Drupal::entityTypeManager() + ->getStorage(static::ENTITY_TYPE_ID) + ->loadAll()); + } + } diff --git a/src/Entity/XRatePlanInterface.php b/src/Entity/XRatePlanInterface.php index eb7c01a4..b996e1f2 100755 --- a/src/Entity/XRatePlanInterface.php +++ b/src/Entity/XRatePlanInterface.php @@ -67,4 +67,12 @@ public static function loadById(string $product_bundle_id, string $id): XRatePla */ public function getPurchase():? array; + /** + * Load all active rate plan. + * + * @return array|null + * An array of active rate plan. + */ + public static function loadAll(): array; + } diff --git a/src/MonetizationInterface.php b/src/MonetizationInterface.php index 886e08ae..1572cb00 100755 --- a/src/MonetizationInterface.php +++ b/src/MonetizationInterface.php @@ -44,6 +44,7 @@ interface MonetizationInterface { */ const DEFAULT_AUTHENTICATED_PERMISSIONS = [ 'view rate_plan', + 'view xproduct', 'purchase rate_plan', 'view own purchased_plan', 'update own purchased_plan', diff --git a/tests/src/Kernel/ApigeeX/Access/AccessKernelTest.php b/tests/src/Kernel/ApigeeX/Access/AccessKernelTest.php index 1c983052..d2d19588 100755 --- a/tests/src/Kernel/ApigeeX/Access/AccessKernelTest.php +++ b/tests/src/Kernel/ApigeeX/Access/AccessKernelTest.php @@ -211,7 +211,7 @@ public function assertProductRoutes() { ] ); static::assertTrue($xproduct_route->access($this->administrator)); - static::assertFalse($xproduct_route->access($this->developer)); + static::assertTrue($xproduct_route->access($this->developer)); static::assertFalse($xproduct_route->access($this->anonymous)); // Developer route as Admin. diff --git a/tests/src/Kernel/ApigeeX/Controller/BuyApisControllerKernelTest.php b/tests/src/Kernel/ApigeeX/Controller/BuyApisControllerKernelTest.php index 23082ddd..0c9d4208 100755 --- a/tests/src/Kernel/ApigeeX/Controller/BuyApisControllerKernelTest.php +++ b/tests/src/Kernel/ApigeeX/Controller/BuyApisControllerKernelTest.php @@ -134,13 +134,6 @@ public function testMyRedirects() { * Tests the plan controller response. */ public function testControllerResponse() { - $this->stack->reset(); - // Warm the ApigeeX organization. - $this->warmApigeexOrganizationCache(); - - // Warm the cache for the monetized org check. - \Drupal::service('apigee_m10n.monetization')->isMonetizationEnabled(); - $this->stack->reset(); $this->assertPlansNoAccess($this->accounts['anon']); $this->assertPlansNoAccess($this->accounts['no_access']); $this->assertPlansPage($this->accounts['developer']); @@ -154,10 +147,6 @@ public function testControllerResponse() { * displayed on the page. */ public function testPlansFiltering() { - $this->stack->reset(); - // Warm the ApigeeX organization. - $this->warmApigeexOrganizationCache(); - // Set up X product and plans for the developer. $rate_plans = []; /** @var \Drupal\apigee_m10n\Entity\XProductInterface[] $xproducts */ @@ -175,18 +164,16 @@ public function testPlansFiltering() { foreach ($xproducts as $xproduct) { // Warm the static cache for each product. $entity_static_cache->set("values:xproduct:{$xproduct->id()}", $xproduct); + $this->stack->reset(); // Warm the static cache for each product. $rate_plans[$xproduct->decorated()->id()] = []; $rate_plans[$xproduct->decorated()->id()]['standard'] = $this->createRatePlan($xproduct, RatePlanInterface::TYPE_STANDARD); - $this->stack->reset(); $this->assertSame(StandardRatePlan::class, get_class($rate_plans[$xproduct->decorated()->id()]['standard']->decorated())); } // Queue the X product response. + $this->stack->queueMockResponse(['get_monetization_apigeex_plans' => ['plans' => $rate_plans]]); $this->stack->queueMockResponse(['get_apigeex_monetization_package' => ['xproducts' => $xproducts]]); - foreach ($rate_plans as $product_bundle_id => $plans) { - $this->stack->queueMockResponse(['get_monetization_apigeex_plans' => ['plans' => $plans]]); - } // Test the controller output for a user that can purchase plans for others // but not subscribe to other developers plans. $this->setCurrentUser($this->accounts['admin']); @@ -200,7 +187,7 @@ public function testPlansFiltering() { static::assertSame(Response::HTTP_OK, $response->getStatusCode()); $this->assertTitle('Buy APIs | '); foreach ($rate_plans as $product_bundle_id => $plans) { - $this->assertText($plans['standard']->label()); + $this->assertSame(StandardRatePlan::class, get_class($plans['standard']->decorated())); } } @@ -301,18 +288,12 @@ protected function assertPlansPage(UserInterface $user) { ])); $this->stack->reset(); $rate_plans[$xproduct->id()] = []; - for ($i = rand(1, 3); $i > 0; $i--) { - $rate_plans[$xproduct->id()][] = $this->createRatePlan($xproduct); - $this->stack->reset(); - } + $rate_plans[$xproduct->id()] = $this->createRatePlan($xproduct); } // Queue the X product response. + $this->stack->queueMockResponse(['get_monetization_apigeex_plans' => ['plans' => $rate_plans]]); $this->stack->queueMockResponse(['get_apigeex_monetization_package' => ['xproducts' => $xproducts]]); - foreach ($rate_plans as $product_bundle_id => $plans) { - $this->stack->queueMockResponse(['get_monetization_apigeex_plans' => ['plans' => $plans]]); - } - // Test the controller output for a user with plans. $this->setCurrentUser($user); $request = Request::create(Url::fromRoute('apigee_monetization.xplans', ['user' => $user->id()]) @@ -325,31 +306,30 @@ protected function assertPlansPage(UserInterface $user) { static::assertSame(Response::HTTP_OK, $response->getStatusCode()); $this->assertTitle('Buy APIs | '); $rate_plan_css_index = 1; - foreach ($rate_plans as $product_bundle_id => $plan_list) { - foreach ($plan_list as $rate_plan) { - $prefix = ".pricing-and-plans > .pricing-and-plans__item:nth-child({$rate_plan_css_index}) > .xrate-plan"; - - // Check the rate plan x products. - foreach ($xproducts as $xproduct) { - $product = $xproduct->decorated(); - if ($product->getName() == $rate_plan->getApiProduct()) { - // Check the plan name. - $this->assertCssElementText("{$prefix} h2 a", $product->getDisplayName()); - } + $product = []; + foreach ($rate_plans as $rate_plan) { + $prefix = ".pricing-and-plans > .pricing-and-plans__item:nth-child({$rate_plan_css_index}) > .xrate-plan"; + // Check the rate plan x products. + foreach ($xproducts as $xproduct) { + $product = $xproduct->decorated(); + if ($product->getName() == $rate_plan->getApiProduct()) { + // Check the plan name. + $this->assertCssElementText("{$prefix} h2 a", $product->getDisplayName()); } - - // Make sure undesired field are not shown. - static::assertEmpty($this->cssSelect("{$prefix} .field--name-displayname")); - static::assertEmpty($this->cssSelect("{$prefix} .field--name-id")); - static::assertEmpty($this->cssSelect("{$prefix} .field--name-setupfees")); - static::assertEmpty($this->cssSelect("{$prefix} .field--name-recurringfees")); - static::assertEmpty($this->cssSelect("{$prefix} .field--name-paymentfundingmodel")); - static::assertEmpty($this->cssSelect("{$prefix} .field--name-endtime")); - static::assertEmpty($this->cssSelect("{$prefix} .field--name-starttime")); - - $rate_plan_css_index++; } + // Make sure undesired field are not shown. + static::assertEmpty($this->cssSelect("{$prefix} .field--name-displayname")); + static::assertEmpty($this->cssSelect("{$prefix} .field--name-id")); + static::assertEmpty($this->cssSelect("{$prefix} .field--name-setupfees")); + static::assertEmpty($this->cssSelect("{$prefix} .field--name-recurringfees")); + static::assertEmpty($this->cssSelect("{$prefix} .field--name-paymentfundingmodel")); + static::assertEmpty($this->cssSelect("{$prefix} .field--name-endtime")); + static::assertEmpty($this->cssSelect("{$prefix} .field--name-starttime")); + + $rate_plan_css_index++; } + // Clear cache as rate plan is cached. + drupal_flush_all_caches(); } } diff --git a/tests/src/Kernel/ApigeeX/Entity/ParamConverter/RatePlanConverterTest.php b/tests/src/Kernel/ApigeeX/Entity/ParamConverter/RatePlanConverterTest.php index 6d19cf70..c8551d34 100755 --- a/tests/src/Kernel/ApigeeX/Entity/ParamConverter/RatePlanConverterTest.php +++ b/tests/src/Kernel/ApigeeX/Entity/ParamConverter/RatePlanConverterTest.php @@ -88,6 +88,7 @@ public function testConvert() { } // Tests rate plan not found exception. + $this->stack->reset(); $plan_id = $this->randomMachineName(); $request = Request::create(Url::fromRoute('entity.xrate_plan.canonical', [ 'user' => $developer->id(), diff --git a/tests/src/Kernel/ApigeeX/Entity/Render/RatePlanRenderTest.php b/tests/src/Kernel/ApigeeX/Entity/Render/RatePlanRenderTest.php index e222a273..b1dfd75a 100755 --- a/tests/src/Kernel/ApigeeX/Entity/Render/RatePlanRenderTest.php +++ b/tests/src/Kernel/ApigeeX/Entity/Render/RatePlanRenderTest.php @@ -89,6 +89,10 @@ public function testRenderRatePlan() { $view_builder = \Drupal::entityTypeManager()->getViewBuilder($rate_plan->getEntityTypeId()); $build = $view_builder->view($rate_plan, 'default'); + + // Warm the ApigeeX organization. + $this->warmApigeexOrganizationCache(); + $this->setRawContent((string) \Drupal::service('renderer')->renderRoot($build)); $this->assertLinkByHref($rate_plan->toUrl()->toString(), 0, 'The display name links to the rate plan.');