You are here

AppForm.php in Apigee Edge 8

File

src/Entity/Form/AppForm.php
View source
<?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_edge\Entity\Form;

use Drupal\apigee_edge\Entity\AppInterface;
use Drupal\apigee_edge\Entity\Controller\AppCredentialControllerInterface;
use Drupal\apigee_edge\Entity\Storage\EdgeEntityStorageBase;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\Core\Utility\Error;

/**
 * Base entity form for developer- and team (company) app create/edit forms.
 */
abstract class AppForm extends FieldableEdgeEntityForm {

  /**
   * Constructs AppCreationForm.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {

    // Ensure entity type manager is always initialized.
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);
    $form['#tree'] = TRUE;

    /** @var \Drupal\apigee_edge\Entity\AppInterface $app */
    $app = $this->entity;

    // By default we render this as a simple text field, sub-classes can change
    // this.
    $form['owner'] = [
      '#title' => $this
        ->t('Owner'),
      '#description' => $this
        ->t("A developer's id (uuid), email address or a team's (company's) name."),
      '#type' => 'textfield',
      '#weight' => -100,
      '#default_value' => $app
        ->getAppOwner(),
      '#required' => TRUE,
    ];
    $form['name'] = [
      '#type' => 'machine_name',
      '#machine_name' => [
        'source' => [
          'displayName',
          'widget',
          0,
          'value',
        ],
        'label' => $this
          ->t('Internal name'),
        'exists' => [
          $this,
          'appExists',
        ],
      ],
      '#title' => $this
        ->t('Internal name'),
      // It should/can not be changed if app is not new.
      '#disabled' => !$app
        ->isNew(),
      '#default_value' => $app
        ->getName(),
    ];
    $form['#attached']['library'][] = 'apigee_edge/apigee_edge.components';
    $form['#attributes']['class'][] = 'apigee-edge--form';
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  protected function actions(array $form, FormStateInterface $form_state) {
    $actions = parent::actions($form, $form_state);
    $actions['submit']['#value'] = $this
      ->saveButtonLabel();
    return $actions;
  }

  /**
   * Returns the list of API product that the user can see on the form.
   *
   * @return \Drupal\apigee_edge\Entity\ApiProductInterface[]
   *   Array of API product entities.
   */
  protected abstract function apiProductList(array $form, FormStateInterface $form_state) : array;

  /**
   * Returns the label of the Save button on the form.
   *
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
   *   The translatable label.
   */
  protected function saveButtonLabel() : TranslatableMarkup {
    return $this
      ->t('Save');
  }

  /**
   * {@inheritdoc}
   */
  public function buildEntity(array $form, FormStateInterface $form_state) {

    /** @var \Drupal\apigee_edge\Entity\AppInterface $entity */
    $entity = parent::buildEntity($form, $form_state);

    // Set the owner of the app. Without this an app can not be saved.
    // @see \Drupal\apigee_edge\Entity\Controller\DeveloperAppEdgeEntityControllerProxy::create()
    $entity
      ->setAppOwner($form_state
      ->getValue('owner'));
    return $entity;
  }

  /**
   * {@inheritdoc}
   */
  public function save(array $form, FormStateInterface $form_state) {

    /** @var \Drupal\apigee_edge\Entity\AppInterface $app */
    $app = $this->entity;
    $was_new = $app
      ->isNew();
    $context = [
      '@app' => $this
        ->appEntityDefinition()
        ->getSingularLabel(),
      '@operation' => $was_new ? $this
        ->t('created') : $this
        ->t('updated'),
    ];

    // First save the app entity on Apigee Edge.
    $result = $this
      ->saveApp();
    if ($result > 0) {

      // Then apply credential changes on the app.
      $credential_save_result = $this
        ->saveAppCredentials($app, $form_state);

      // If credential save result is either successful (TRUE) or NULL
      // (no operation performed because there were no change in API product
      // association) then we consider the app save as successful.
      if ($credential_save_result ?? TRUE) {
        $this
          ->messenger()
          ->addStatus($this
          ->t('@app has been successfully @operation.', $context));

        // Also, if app credential(s) could be successfully saved as well then
        // display an extra confirmation message about this.
        if ($credential_save_result === TRUE) {
          $this
            ->messenger()
            ->addStatus($this
            ->t("Credential's product list has been successfully updated."));
        }

        // Only redirect the user from the add/edit form if all operations
        // could be successfully performed.
        $form_state
          ->setRedirectUrl($this
          ->getRedirectUrl());
      }
      else {

        // Display different error messages on app create/edit.
        if ($was_new) {
          $this
            ->messenger()
            ->addError($this
            ->t('Unable to set up credentials on the created app. Please try again.'));
        }
        else {
          $this
            ->messenger()
            ->addError($this
            ->t('Unable to update credentials on the app. Please try again.'));
        }
      }
    }
    else {
      $this
        ->messenger()
        ->addError($this
        ->t('@app could not be @operation. Please try again.', $context));
    }
    return $result;
  }

  /**
   * Saves the app entity on Apigee Edge.
   *
   * It should log failures but it should not display messages to users.
   * This is handled in save().
   *
   * @return int
   *   SAVED_NEW, SAVED_UPDATED or SAVED_UNKNOWN.
   */
  protected function saveApp() : int {

    /** @var \Drupal\apigee_edge\Entity\AppInterface $app */
    $app = $this->entity;
    $was_new = $app
      ->isNew();
    try {
      $result = $app
        ->save();
    } catch (EntityStorageException $exception) {
      $context = [
        '%app_name' => $app
          ->label(),
        '%owner' => $app
          ->getAppOwner(),
        '@app' => mb_strtolower($this
          ->appEntityDefinition()
          ->getSingularLabel()),
        '@owner' => mb_strtolower($this
          ->appOwnerEntityDefinition()
          ->getSingularLabel()),
        '@operation' => $was_new ? $this
          ->t('create') : $this
          ->t('update'),
      ];
      $context += Error::decodeException($exception);
      $this
        ->logger('apigee_edge')
        ->critical('Could not @operation %app_name @app of %owner @owner. @message %function (line %line of %file). <pre>@backtrace_string</pre>', $context);
      $result = EdgeEntityStorageBase::SAVED_UNKNOWN;
    }
    return $result;
  }

  /**
   * Save app credentials on Apigee Edge.
   *
   * It should log failures but it should not display messages to users.
   * This is handled in save().
   *
   * @param \Drupal\apigee_edge\Entity\AppInterface $app
   *   The app entity which credentials gets updated.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object with the credential related changes.
   *
   * @return bool|null
   *   TRUE on success, FALSE or failure, NULL if no action performed (because
   *   credentials did not change).
   */
  protected abstract function saveAppCredentials(AppInterface $app, FormStateInterface $form_state) : ?bool;

  /**
   * Returns the URL where the user should be redirected after form submission.
   *
   * @return \Drupal\Core\Url
   *   The redirect URL.
   */
  protected function getRedirectUrl() : Url {
    $entity = $this
      ->getEntity();
    if ($entity
      ->hasLinkTemplate('collection')) {

      // If available, return the collection URL.
      return $entity
        ->toUrl('collection');
    }
    else {

      // Otherwise fall back to the front page.
      return Url::fromRoute('<front>');
    }
  }

  /**
   * Returns the app specific app credential controller.
   *
   * @param string $owner
   *   The developer id (UUID), email address or team (company) name.
   * @param string $app_name
   *   The name of an app.
   *
   * @return \Drupal\apigee_edge\Entity\Controller\AppCredentialControllerInterface
   *   The app credential controller.
   */
  protected abstract function appCredentialController(string $owner, string $app_name) : AppCredentialControllerInterface;

  /**
   * Checks if the owner already has an app with the same name.
   *
   * @param string $name
   *   The app name.
   * @param array $element
   *   Form element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state.
   *
   * @return bool
   *   TRUE if the owner already has an app with the provided name or in case
   *   if an API communication error, FALSE otherwise.
   */
  public static abstract function appExists(string $name, array $element, FormStateInterface $form_state) : bool;

  /**
   * Returns the default lifetime of a created app credential.
   *
   * @return int
   *   App credential lifetime in seconds, 0 for never expire.
   */
  protected abstract function appCredentialLifeTime() : int;

  /**
   * Returns the developer/team (company) app entity definition.
   *
   * @return \Drupal\Core\Entity\EntityTypeInterface
   *   The app entity definition.
   */
  protected abstract function appEntityDefinition() : EntityTypeInterface;

  /**
   * Returns the app owner (developer or team (company)) entity definition.
   *
   * @return \Drupal\Core\Entity\EntityTypeInterface
   *   The app owner entity definition.
   */
  protected abstract function appOwnerEntityDefinition() : EntityTypeInterface;

  /**
   * {@inheritdoc}
   */
  public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {

    // Always expect that the parameter in the route is not the entity type
    // (ex: {developer_app}) in case of apps rather just {app}.
    if ($route_match
      ->getRawParameter('app') !== NULL) {
      $entity = $route_match
        ->getParameter('app');
    }
    else {
      $entity = parent::getEntityFromRouteMatch($route_match, $entity_type_id);
    }
    return $entity;
  }

}

Classes

Namesort descending Description
AppForm Base entity form for developer- and team (company) app create/edit forms.