apigee_m10n-8.x-1.7/src/Monetization.php

src/Monetization.php
<?php

/*
 * 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;

use Apigee\Edge\Api\ApigeeX\Controller\ApiProductController as ApiXProductController;
use Apigee\Edge\Api\ApigeeX\Controller\PrepaidBalanceControllerInterface as PrepaidBalanceXControllerInterface;
use Apigee\Edge\Api\ApigeeX\Entity\DeveloperBillingType;
use Apigee\Edge\Api\Management\Entity\OrganizationInterface;
use Apigee\Edge\Api\Monetization\Controller\ApiProductController;
use Apigee\Edge\Api\Monetization\Controller\PrepaidBalanceControllerInterface;
use Apigee\Edge\Api\Monetization\Entity\CompanyInterface;
use Apigee\Edge\Api\Monetization\Entity\TermsAndConditionsInterface;
use Apigee\Edge\Api\Monetization\Structure\LegalEntityTermsAndConditionsHistoryItem;
use Apigee\Edge\Api\Monetization\Structure\Reports\Criteria\PrepaidBalanceReportCriteria;
use CommerceGuys\Intl\Formatter\CurrencyFormatterInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\apigee_edge\Entity\Controller\OrganizationControllerInterface;
use Drupal\apigee_edge\SDKConnectorInterface;
use Drupal\apigee_m10n\Entity\PurchasedPlan;
use Drupal\apigee_m10n\Entity\PurchasedProduct;
use Drupal\apigee_m10n\Entity\RatePlanInterface;
use Drupal\apigee_m10n\Entity\XRatePlanInterface;
use Drupal\apigee_m10n\Exception\SdkEntityLoadException;
use Drupal\user\PermissionHandlerInterface;
use Drupal\user\RoleInterface;
use Drupal\user\UserInterface;
use Psr\Log\LoggerInterface;

/**
 * Apigee Monetization base service.
 */
class Monetization implements MonetizationInterface {

  const MONETIZATION_DISABLED_ERROR_MESSAGE = 'Monetization is not enabled for your Apigee Edge organization.';

  /**
   * The Apigee Edge SDK connector.
   *
   * @var \Drupal\apigee_edge\SDKConnectorInterface
   */
  private $sdkConnector;

  /**
   * The SDK controller factory.
   *
   * @var \Drupal\apigee_m10n\ApigeeSdkControllerFactoryInterface
   */
  private $sdkControllerFactory;

  /**
   * Drupal core messenger service (for adding flash messages).
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  private $messenger;

  /**
   * The Cache backend.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  private $cache;

  /**
   * The logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  private $logger;

  /**
   * The currency formatter.
   *
   * @var \CommerceGuys\Intl\Formatter\CurrencyFormatter
   */
  private $currencyFormatter;

  /**
   * Static cache of `acceptLatestTermsAndConditions` results.
   *
   * @var array
   */
  private $developerAcceptedTermsStatus;

  /**
   * Static cache of the latest TnC.
   *
   * @var \Apigee\Edge\Api\Monetization\Entity\TermsAndConditionsInterface|false
   */
  protected $latestTermsAndConditions;

  /**
   * Static cache of the TnC list.
   *
   * @var \Apigee\Edge\Api\Monetization\Entity\TermsAndConditionsInterface[]
   */
  protected $termsAndConditionsList;

  /**
   * The permission handler.
   *
   * @var \Drupal\user\PermissionHandlerInterface
   */
  protected $permission_handler;

  /**
   * The management organization controller.
   *
   * @var \Drupal\apigee_edge\Entity\Controller\OrganizationControllerInterface
   */
  protected $organizationController;

  /**
   * The management organization entity.
   *
   * @var \Apigee\Edge\Api\Management\Entity\OrganizationInterface
   */
  protected $organization;

  /**
   * Monetization constructor.
   *
   * @param \Drupal\apigee_edge\SDKConnectorInterface $sdk_connector
   *   The Apigee Edge SDK connector.
   * @param \Drupal\apigee_m10n\ApigeeSdkControllerFactoryInterface $sdk_controller_factory
   *   The SDK controller factory.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   Drupal core messenger service (for adding flash messages).
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
   *   The Cache backend.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger.
   * @param \Drupal\user\PermissionHandlerInterface $permission_handler
   *   The permission handler.
   * @param \CommerceGuys\Intl\Formatter\CurrencyFormatterInterface $currency_formatter
   *   A currency formatter.
   * @param \Drupal\apigee_edge\Entity\Controller\OrganizationControllerInterface $organization_controller
   *   The management organization controller.
   */
  public function __construct(
    SDKConnectorInterface $sdk_connector,
    ApigeeSdkControllerFactoryInterface $sdk_controller_factory,
    MessengerInterface $messenger,
    CacheBackendInterface $cache,
    LoggerInterface $logger,
    PermissionHandlerInterface $permission_handler,
    CurrencyFormatterInterface $currency_formatter,
    OrganizationControllerInterface $organization_controller,
  ) {
    $this->sdkConnector           = $sdk_connector;
    $this->sdkControllerFactory   = $sdk_controller_factory;
    $this->messenger              = $messenger;
    $this->cache                  = $cache;
    $this->logger                 = $logger;
    $this->permission_handler     = $permission_handler;
    $this->currencyFormatter      = $currency_formatter;
    $this->organizationController = $organization_controller;
  }

  /**
   * {@inheritdoc}
   */
  public function isMonetizationEnabled(): bool {
    $org = $this->getOrganization();
    if ($this->isOrganizationApigeeXorHybrid($org) && $org->getAddonsConfig() && $org->getAddonsConfig()->getMonetizationConfig()) {
      return ($org && TRUE === $org->getAddonsConfig()->getMonetizationConfig()->getEnabled());
    }
    else {
      return ($org && $org->getPropertyValue('features.isMonetizationEnabled') === 'true');
    }
  }

  /**
   * {@inheritdoc}
   */
  public function apiProductAssignmentAccess(EntityInterface $entity, AccountInterface $account): AccessResultInterface {
    // Cache results for this request.
    static $eligible_product_cache = [];
    // The developer ID to check access for.
    $developer_id = $account->getEmail();

    if (!isset($eligible_product_cache[$developer_id])) {
      if ($this->isOrganizationApigeeXorHybrid()) {
        // Instantiate an instance of the m10n ApiProduct controller.
        $product_controller = new ApiXProductController($this->sdkConnector->getOrganization(), $this->sdkConnector->getClient());
        // Get a list of available products for the m10n developer.
        $eligible_product_cache[$developer_id] = $product_controller->getEligibleProductsByDeveloper($developer_id);
      }
      else {
        // Instantiate an instance of the m10n ApiProduct controller.
        $product_controller = new ApiProductController($this->sdkConnector->getOrganization(), $this->sdkConnector->getClient());
        // Get a list of available products for the m10n developer.
        $eligible_product_cache[$developer_id] = $product_controller->getEligibleProductsByDeveloper($developer_id);
      }
    }

    // Get just the IDs from the available products.
    $product_ids = array_map(function ($product) {
      return $product->id();
    }, $eligible_product_cache[$developer_id]);

    if ($this->isOrganizationApigeeXorHybrid()) {
      // Apigee X products are case sensitive.
      return in_array(($entity->id()), $product_ids)
        ? AccessResult::allowed()
        : AccessResult::forbidden('Product is not eligible for this developer');
    }
    else {
      // Allow only if the id is in the eligible list.
      return in_array(strtolower($entity->id()), $product_ids)
        ? AccessResult::allowed()
        : AccessResult::forbidden('Product is not eligible for this developer');
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getDeveloperPrepaidBalances(UserInterface $developer, \DateTimeImmutable $billingDate): ?array {
    $balance_controller = $this->sdkControllerFactory->developerBalanceController($developer);
    return $this->getPrepaidBalances($balance_controller, $billingDate);
  }

  /**
   * {@inheritdoc}
   */
  public function getDeveloperPrepaidBalancesX(UserInterface $developer): ?array {
    $balance_controller = $this->sdkControllerFactory->developerBalancexController($developer);
    return $this->getPrepaidBalancesX($balance_controller);
  }

  /**
   * {@inheritdoc}
   */
  public function getCompanyPrepaidBalances(CompanyInterface $company, \DateTimeImmutable $billingDate): ?array {
    $balance_controller = $this->sdkControllerFactory->companyBalanceController($company);
    return $this->getPrepaidBalances($balance_controller, $billingDate);
  }

  /**
   * {@inheritdoc}
   */
  public function isLatestTermsAndConditionAccepted(string $developer_id): ?bool {
    if (!($latest_tnc = $this->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->developerAcceptedTermsStatus[$developer_id])) {
      // Get the latest TnC ID.
      $latest_tnc_id = $latest_tnc->id();

      // Creates a controller for getting accepted TnC.
      $controller = $this->sdkControllerFactory->developerTermsAndConditionsController($developer_id);

      try {
        $history = $controller->getTermsAndConditionsHistory();
      }
      catch (\Exception $e) {
        $message = "Unable to load Terms and Conditions history for developer \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->developerAcceptedTermsStatus[$developer_id] = ($latest instanceof LegalEntityTermsAndConditionsHistoryItem) && $latest->getAction() === 'ACCEPTED';
    }

    return $this->developerAcceptedTermsStatus[$developer_id];
  }

  /**
   * {@inheritdoc}
   */
  public function getLatestTermsAndConditions(): ?TermsAndConditionsInterface {
    // Check the static cache.
    if (isset($this->latestTermsAndConditions)) {
      return $this->latestTermsAndConditions;
    }
    // Get the full list.
    $list = $this->getTermsAndConditionsList();

    // Get the latest TnC that have already started.
    $latest = empty($list) ? NULL : array_reduce($list, function ($carry, $item) {
      /** @var \Apigee\Edge\Api\Monetization\Entity\TermsAndConditionsInterface $item */
      // Gets the time of the carry over item.
      $carry_time = $carry instanceof TermsAndConditionsInterface ? $carry->getStartDate()->getTimestamp() : NULL;
      // Gets the timestamp of the current item.
      $item_time = $item->getStartDate()->getTimestamp();
      $now = time();
      // Return the current item only if it the latest without starting in the
      // future.
      return ($item_time > $carry_time && $item_time < $now) ? $item : $carry;
    });

    // Cache the result for this request.
    $this->latestTermsAndConditions = $latest;

    return $this->latestTermsAndConditions;
  }

  /**
   * Gets the full list of terms and conditions.
   *
   * @return \Apigee\Edge\Api\Monetization\Entity\TermsAndConditionsInterface[]
   *   Returns the full list of terms and conditions or false on error.
   */
  protected function getTermsAndConditionsList(): array {
    // The cache ID.
    $cid = 'apigee_m10n:terms_and_conditions_list';

    // Check the static cache.
    if (isset($this->termsAndConditionsList)) {
      return $this->termsAndConditionsList;
    }
    // Check the cache.
    elseif (($cache = $this->cache->get($cid)) && ($list = $cache->data)) {
      // `$list` is set so there is nothing to do here.
    }
    else {
      try {
        $list = $this->sdkControllerFactory->termsAndConditionsController()->getEntities();
      }
      catch (\Exception $ex) {
        $this->logger->error("Unable to load Terms and Conditions: \n {$ex}");
        $this->cache->delete($cid);
        throw new SdkEntityLoadException("Error loading Terms and conditions. \n\n" . $ex);
      }

      // Cache the list for 5 minutes.
      $this->cache->set($cid, $list, time() + 299);

    }
    $this->termsAndConditionsList = $list;

    return $this->termsAndConditionsList;
  }

  /**
   * {@inheritdoc}
   */
  public function acceptLatestTermsAndConditions(string $developer_id): ?LegalEntityTermsAndConditionsHistoryItem {
    try {
      // Reset the static cache for this developer.
      unset($this->developerAcceptedTermsStatus[$developer_id]);
      return $this->sdkControllerFactory->developerTermsAndConditionsController($developer_id)
        ->acceptTermsAndConditionsById($this->getLatestTermsAndConditions()->id());
    }
    catch (\Throwable $t) {
      $this->logger->error('Unable to accept latest TnC: ' . $t->getMessage());
    }
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function formatCurrency(string $amount, string $currency_id): string {
    return $this->currencyFormatter->format($amount, $currency_id);
  }

  /**
   * Gets the prepaid balances.
   *
   * Uses a prepaid balance controller to return prepaid balances for a
   * specified month and year.
   *
   * @param \Apigee\Edge\Api\Monetization\Controller\PrepaidBalanceControllerInterface $balance_controller
   *   The balance controller.
   * @param \DateTimeImmutable $billingDate
   *   The time to get the report for.
   *
   * @return \Apigee\Edge\Api\Monetization\Entity\PrepaidBalanceInterface[]|null
   *   The balance list or null if no balances are available.
   */
  protected function getPrepaidBalances(PrepaidBalanceControllerInterface $balance_controller, \DateTimeImmutable $billingDate): ?array {

    try {
      $result = $balance_controller->getPrepaidBalance($billingDate);
    }
    catch (\Exception $e) {
      $this->messenger->addWarning($e->getMessage());
      $this->logger->warning('Unable to retrieve prepaid balances: ' . $e->getMessage());
      return NULL;
    }

    return $result;
  }

  /**
   * Gets the prepaid balances.
   *
   * Uses a prepaid balance controller to return prepaid balances for a
   * specified month and year.
   *
   * @param \Apigee\Edge\Api\ApigeeX\Controller\PrepaidBalanceXControllerInterface $balance_controller
   *   The balance controller.
   *
   * @return \Apigee\Edge\Api\ApigeeX\Entity\PrepaidBalanceInterface[]|null
   *   The balance list or null if no balances are available.
   */
  protected function getPrepaidBalancesX(PrepaidBalanceXControllerInterface $balance_controller): ?array {

    try {
      $result = $balance_controller->getPrepaidBalance();
    }
    catch (\Exception $e) {
      $this->messenger->addWarning($e->getMessage());
      $this->logger->warning('Unable to retrieve prepaid balances: ' . $e->getMessage());
      return NULL;
    }

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function getSupportedCurrencies(): ?array {
    if ($this->isOrganizationApigeeXorHybrid()) {
      return $this->sdkControllerFactory->supportedCurrencyxController()->getEntities();
    }
    else {
      return $this->sdkControllerFactory->supportedCurrencyController()->getEntities();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getPrepaidBalanceReport(string $developer_id, \DateTimeImmutable $date, string $currency): ?string {
    $controller = $this->sdkControllerFactory->developerReportDefinitionController($developer_id);
    $criteria = new PrepaidBalanceReportCriteria(strtoupper($date->format('F')), (int) $date->format('Y'));
    $criteria
      ->setDevelopers($developer_id)
      ->setCurrencies($currency)
      ->setShowTransactionDetail(TRUE);
    return $controller->generateReport($criteria);
  }

  /**
   * {@inheritdoc}
   */
  public function isDeveloperAlreadySubscribed(string $developer_id, RatePlanInterface $rate_plan): bool {
    // Use cached result if available.
    // @todo Handle purchased_plan caching per developer on the storage level.
    // See: \Drupal\apigee_m10n\Entity\Storage\PurchasedPlanStorage::loadByDeveloperId()
    $cid = "apigee_m10n:dev:purchased_plans:{$developer_id}";
    if ($cache = $this->cache->get($cid)) {
      $purchases = $cache->data;
    }
    else {
      $purchases = PurchasedPlan::loadByDeveloperId($developer_id);
      $this->cache->set($cid, $purchases, strtotime('now + 5 minutes'));
    }

    foreach ($purchases as $purchased_plan) {
      if ($purchased_plan->getRatePlan()->id() == $rate_plan->id() && $purchased_plan->isActive()) {
        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function isDeveloperAlreadySubscribedX(string $developer_id, XRatePlanInterface $rate_plan): bool {
    $purchases = PurchasedProduct::loadByDeveloperId($developer_id);

    foreach ($purchases as $purchased_product) {
      if (($purchased_product->decorated()->getApiProduct() == $rate_plan->decorated()->getApiProduct()) && (empty($purchased_product->decorated()->getEndTime()))) {
        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function isDeveloperPrepaid(UserInterface $account): bool {

    // Use cached result if available.
    $cid = "apigee_m10n:dev:billing_type:{$account->getEmail()}";

    if ($cache = $this->cache->get($cid)) {
      $billing_type = $cache->data;
    }
    else {
      $config = \Drupal::service('config.factory');
      $cacheExpiration = $config->get('apigee_edge.developer_settings')->get('cache_expiration');
      /** @var \Apigee\Edge\Api\Monetization\Entity\DeveloperInterface $developer */
      $developer = $this->sdkControllerFactory->developerController()->load($account->getEmail());
      $billing_type = $developer->getBillingType();

      if ($cacheExpiration < 0) {
        $this->cache->set($cid, $billing_type);
      }
      elseif ($cacheExpiration > 0) {
        $this->cache->set($cid, $billing_type, strtotime('now + ' . $cacheExpiration . ' seconds'));
      }
    }

    return $billing_type == 'PREPAID';
  }

  /**
   * {@inheritdoc}
   */
  public function isDeveloperPrepaidX(UserInterface $account): bool {

    // Use cached result if available.
    $cid = "apigee_m10n:dev:billing_type:{$account->getEmail()}";

    if ($cache = $this->cache->get($cid)) {
      $billing_type = $cache->data;
    }
    else {
      $config = \Drupal::service('config.factory');
      $cacheExpiration = $config->get('apigee_edge.developer_settings')->get('cache_expiration');
      /** @var \Apigee\Edge\Api\Monetization\Entity\DeveloperInterface $developer */
      try {
        $developer = $this->sdkControllerFactory->developerBillingTypeController($account->getEmail())->getAllBillingDetails();
      }
      catch (\Exception $e) {
        $this->messenger->addError($e->getMessage());
        $this->logger->warning('Unable to retrieve billing type: ' . $e->getMessage());
        return FALSE;
      }

      $billing_type = $developer->getbillingType();

      if ($cacheExpiration < 0) {
        $this->cache->set($cid, $billing_type);
      }
      elseif ($cacheExpiration > 0) {
        $this->cache->set($cid, $billing_type, strtotime('now + ' . $cacheExpiration . ' seconds'));
      }
    }

    return $billing_type == 'PREPAID';
  }

  /**
   * {@inheritdoc}
   */
  public function updateBillingtype(string $developer_email, string $billingtype): DeveloperBillingType {
    return $this->sdkControllerFactory->developerBillingTypeController($developer_email)->updateBillingType($billingtype);
  }

  /**
   * {@inheritdoc}
   */
  public function getBillingtype(UserInterface $user): ?string {
    return $this->sdkControllerFactory->developerBillingTypeController($user->getEmail())->getAllBillingDetails()->getbillingType();
  }

  /**
   * {@inheritdoc}
   */
  public function formUserAdminPermissionsAlter(&$form, FormStateInterface $form_state, $form_id) {
    // Disable All incompatible permissions in the UI.
    foreach (array_keys($this->getMonetizationPermissions()) as $permission_name) {
      if (isset($form['permissions'][$permission_name][AccountInterface::ANONYMOUS_ROLE])) {
        // Disable the permission.
        $form['permissions'][$permission_name][AccountInterface::ANONYMOUS_ROLE]['#disabled'] = TRUE;
        $form['permissions'][$permission_name][AccountInterface::ANONYMOUS_ROLE]['#value'] = 0;
      }
    }
    // These permissions are provided by 3rd party modules and aren't needed.
    // Since there is no hook_permissions_alter, we can at least remove them
    // from the admin form.
    $unused_permissions = [
      'administer product_bundle fields',
      'administer product_bundle form display',
      'administer rate_plan fields',
      'administer rate_plan form display',
      'administer purchased_plan fields',
    ];
    foreach ($unused_permissions as $unused_permission) {
      if (isset($form['permissions'][$unused_permission])) {
        $form['permissions'][$unused_permission]['#access'] = FALSE;
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function userRolePresave(RoleInterface $user_role) {
    // This prevents monetization permission grants on config import.
    if ($user_role->id() === AccountInterface::ANONYMOUS_ROLE) {
      // Get any permissions anon shouldn't have.
      $unauthorized_perms = array_intersect($user_role->getPermissions(), array_keys($this->getMonetizationPermissions()));
      // Remove all unauthorized perms.
      foreach ($unauthorized_perms as $permission_name) {
        $user_role->revokePermission($permission_name);
        $this->logger->info('Removing the `%permission` permission from the anonymous role.', ['%permission' => $permission_name]);
      }
    }

  }

  /**
   * {@inheritdoc}
   */
  public function getOrganization(): ?OrganizationInterface {
    // Check if the organization is statically cached.
    if ($this->organization) {
      return $this->organization;
    }
    // Check the DB cache.
    $cid = 'apigee_m10n:organization';
    if (($cache = $this->cache->get($cid)) && !empty($cache->data)) {
      $this->organization = $cache->data;
    }
    else {
      // Load the org and cache it for 5 minutes.
      try {
        $this->organization = $this->organizationController->load($this->sdkConnector->getOrganization());
      }
      catch (\Exception $e) {
        $this->messenger->addError($e->getMessage());
      }
      $this->cache->set($cid, $this->organization, strtotime("+5 minutes"));
    }

    return $this->organization;
  }

  /**
   * {@inheritdoc}
   */
  public function isOrganizationApigeeX(): bool {
    $org = $this->getOrganization();
    return ($org && 'CLOUD' === $org->getRuntimeType());
  }

  /**
   * {@inheritdoc}
   */
  public function isOrganizationApigeeXorHybrid(): bool {
    $org = $this->getOrganization();
    return ($org && ('CLOUD' === $org->getRuntimeType() || 'HYBRID' === $org->getRuntimeType()));
  }

  /**
   * Gets a list of Apigee monetization permissions.
   *
   * These permissions should only be applied to authenticated roles.
   *
   * @return array
   *   Permissions for the monetization module.
   */
  protected function getMonetizationPermissions() {
    return array_filter($this->permission_handler->getPermissions(), function ($permission) {
      return ($permission['provider'] === 'apigee_m10n');
    });
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc