Skip to content

Commit

Permalink
Merge pull request #3 from Jaesin/8.x-1.x-DRUP-624
Browse files Browse the repository at this point in the history
[DRUP-624] Add revisions to API Docs.
  • Loading branch information
cnovak committed May 26, 2019
2 parents 8da2d7c + f7005aa commit 3b82466
Show file tree
Hide file tree
Showing 9 changed files with 528 additions and 41 deletions.
6 changes: 6 additions & 0 deletions apigee_api_catalog.links.task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,9 @@ apidoc.admin:
route_name: entity.apidoc.collection
base_route: system.admin_content
weight: 10

entity.apidoc.version_history:
route_name: entity.apidoc.version_history
base_route: entity.apidoc.canonical
title: 'Revisions'
weight: 15
8 changes: 8 additions & 0 deletions apigee_api_catalog.permissions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ view published apidoc entities:

view unpublished apidoc entities:
title: 'View unpublished API docs'

view apidoc revisions:
title: 'View API Doc revisions'
description: 'To view a revision, you also need permission to view the item.'

revert apidoc revisions:
title: 'Revert API Doc revisions'
description: 'To revert a revision, you also need permission to edit the item.'
87 changes: 86 additions & 1 deletion src/Entity/Access/ApiDocAccessControlHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,50 @@
namespace Drupal\apigee_api_catalog\Entity\Access;

use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Access controller for the API Doc entity.
*
* @see \Drupal\apigee_api_catalog\Entity\ApiDoc.
*/
class ApiDocAccessControlHandler extends EntityAccessControlHandler {
class ApiDocAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface {

/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;

/**
* Constructs an access control handler instance.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
*/
public function __construct(EntityTypeInterface $entity_type, EntityTypeManagerInterface $entityTypeManager) {
parent::__construct($entity_type);
$this->entityTypeManager = $entityTypeManager;
}

/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity_type.manager')
);
}

/**
* {@inheritdoc}
Expand All @@ -40,6 +74,11 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter

if (!$parent_access->isAllowed()) {
/** @var \Drupal\apigee_api_catalog\Entity\ApiDocInterface $entity */

// Access control for revisions.
if (!$entity->isDefaultRevision()) {
return $this->checkAccessRevisions($entity, $operation, $account);
}
switch ($operation) {
case 'view':
if (!$entity->isPublished()) {
Expand All @@ -59,6 +98,52 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
return $parent_access;
}

/**
* Additional access control for revisions.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to check access.
* @param string $operation
* The entity operation.
* @param \Drupal\Core\Session\AccountInterface $account
* The user for which to check access.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
protected function checkAccessRevisions(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\Core\Entity\EntityStorageInterface $entity_storage */
$entity_storage = $this->entityTypeManager->getStorage($this->entityTypeId);

// Must have access to the same operation on the default revision.
$default_revision = $entity_storage->load($entity->id());
$has_default_entity_rev_access = $default_revision->access($operation, $account);
if (!$has_default_entity_rev_access) {
return AccessResult::forbidden();
}

$map = [
'view' => "view apidoc revisions",
'update' => "revert apidoc revisions",
];

if (!$entity || !isset($map[$operation])) {
// If there was no entity to check against, or the $op was not one of the
// supported ones, we return access denied.
return AccessResult::forbidden();
}

$admin_permission = $this->entityType->getAdminPermission();

// Perform basic permission checks first.
if ($account->hasPermission($map[$operation]) ||
($admin_permission && $account->hasPermission($admin_permission))) {
return AccessResult::allowed();
}

return AccessResult::forbidden();
}

/**
* {@inheritdoc}
*/
Expand Down
88 changes: 73 additions & 15 deletions src/Entity/ApiDoc.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@

namespace Drupal\apigee_api_catalog\Entity;

use Drupal\Core\Entity\EditorialContentEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityTypeInterface;

Expand All @@ -47,30 +49,43 @@
* "access" = "Drupal\apigee_api_catalog\Entity\Access\ApiDocAccessControlHandler",
* "route_provider" = {
* "html" = "Drupal\apigee_api_catalog\Entity\Routing\ApiDocHtmlRouteProvider",
* "revision" = "Drupal\entity\Routing\RevisionRouteProvider",
* },
* },
* base_table = "apidoc",
* data_table = "apidoc_field_data",
* revision_table = "apidoc_revision",
* revision_data_table = "apidoc_field_revision",
* show_revision_ui = TRUE,
* translatable = TRUE,
* admin_permission = "administer apigee api catalog",
* entity_keys = {
* "id" = "id",
* "label" = "name",
* "uuid" = "uuid",
* "langcode" = "langcode",
* "status" = "status",
* "published" = "status",
* "revision" = "revision_id",
* },
* revision_metadata_keys = {
* "revision_user" = "revision_user",
* "revision_created" = "revision_created",
* "revision_log_message" = "revision_log_message",
* },
* links = {
* "canonical" = "/api/{apidoc}",
* "add-form" = "/admin/content/api/add",
* "edit-form" = "/admin/content/api/{apidoc}/edit",
* "delete-form" = "/admin/content/api/{apidoc}/delete",
* "version-history" = "/admin/content/api/{apidoc}/revisions",
* "revision" = "/admin/content/api/{apidoc}/revisions/{apidoc_revision}/view",
* "revision-revert-form" = "/admin/content/api/{apidoc}/revisions/{apidoc_revision}/revert",
* "collection" = "/admin/content/apis",
* },
* field_ui_base_route = "entity.apidoc.settings"
* )
*/
class ApiDoc extends ContentEntityBase implements ApiDocInterface {
class ApiDoc extends EditorialContentEntityBase implements ApiDocInterface {

use EntityChangedTrait;

Expand Down Expand Up @@ -122,16 +137,41 @@ public function setCreatedTime(int $timestamp) : ApiDocInterface {
/**
* {@inheritdoc}
*/
public function isPublished() : bool {
return (bool) $this->getEntityKey('status');
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);

// If no revision author has been set explicitly, make the current user
// the revision author.
if (!$this->getRevisionUser()) {
$this->setRevisionUserId(\Drupal::currentUser()->id());
}
}

/**
* {@inheritdoc}
*/
public function setPublished(bool $published) : ApiDocInterface {
$this->set('status', $published);
return $this;
public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
parent::preSaveRevision($storage, $record);

if (!$this->isNewRevision() && isset($this->original) && empty($record->revision_log_message)) {
// If we are updating an existing entity without adding a new revision, we
// need to make sure $entity->revision_log_message is reset whenever it is
// empty. Therefore, this code allows us to avoid clobbering an existing
// log entry with an empty one.
$record->revision_log_message = $this->original->revision_log_message->value;
}
}

/**
* Gets whether a new revision should be created by default.
*
* @return bool
* TRUE if a new revision should be created by default.
*/
public function shouldCreateNewRevision() {
$config = \Drupal::config('apigee_api_catalog.settings');
$default_revision = $config->get('default_revision');
return is_null($default_revision) ? TRUE : (bool) $default_revision;
}

/**
Expand All @@ -143,6 +183,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setDescription(t('The name of the API.'))
->setRevisionable(TRUE)
->setSettings([
'max_length' => 50,
'text_processing' => 0,
Expand All @@ -164,6 +205,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['description'] = BaseFieldDefinition::create('text_long')
->setLabel(t('Description'))
->setDescription(t('Description of the API.'))
->setRevisionable(TRUE)
->setDisplayOptions('view', [
'label' => 'hidden',
'type' => 'text_default',
Expand All @@ -177,8 +219,9 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDisplayConfigurable('form', TRUE);

$fields['spec'] = BaseFieldDefinition::create('file')
->setLabel('OpenAPI specification')
->setDescription('The spec snapshot.')
->setLabel(t('OpenAPI specification'))
->setDescription(t('The spec snapshot.'))
->setRevisionable(TRUE)
->setSettings([
'file_directory' => 'apidoc_specs',
'file_extensions' => 'yml yaml json',
Expand All @@ -202,7 +245,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {

$fields['api_product'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('API Product'))
->setDescription(t('The API Product this API is associated to.'))
->setDescription(t('The API Product this API is associated with.'))
->setRevisionable(TRUE)
->setSetting('target_type', 'api_product')
->setDisplayOptions('form', [
'label' => 'above',
Expand All @@ -212,24 +256,38 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);

$fields['status'] = BaseFieldDefinition::create('boolean')
$fields['status']
->setLabel(t('Publishing status'))
->setDescription(t('A boolean indicating whether the API Doc is published.'))
->setDefaultValue(TRUE)
->setDisplayOptions('form', [
'type' => 'boolean_checkbox',
'weight' => 1,
]);

$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Created'))
->setDescription(t('The time that the entity was created.'));
->setDescription(t('The time that the entity was created.'))
->setRevisionable(TRUE);

$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time that the entity was last edited.'));
->setDescription(t('The time that the entity was last edited.'))
->setRevisionable(TRUE);

return $fields;
}

/**
* {@inheritdoc}
*/
protected function urlRouteParameters($rel) {
$uri_route_parameters = parent::urlRouteParameters($rel);

if ($rel === 'revision-revert-form' && $this instanceof RevisionableInterface) {
$uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId();
}

return $uri_route_parameters;
}

}
24 changes: 2 additions & 22 deletions src/Entity/ApiDocInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityPublishedInterface;

/**
* Provides an interface for defining API Doc entities.
*/
interface ApiDocInterface extends ContentEntityInterface, EntityChangedInterface {
interface ApiDocInterface extends ContentEntityInterface, EntityChangedInterface, EntityPublishedInterface {

/**
* Gets the API Doc name.
Expand Down Expand Up @@ -85,25 +86,4 @@ public function getCreatedTime() : int;
*/
public function setCreatedTime(int $timestamp) : self;

/**
* Returns the API Doc published status indicator.
*
* Unpublished API Doc are only visible to restricted users.
*
* @return bool
* TRUE if the API Doc is published.
*/
public function isPublished() : bool;

/**
* Sets the published status of a API Doc.
*
* @param bool $published
* TRUE to set this API Doc to published, FALSE to set it to unpublished.
*
* @return \Drupal\apigee_api_catalog\Entity\ApiDocInterface
* The called API Doc entity.
*/
public function setPublished(bool $published) : self;

}
12 changes: 12 additions & 0 deletions src/Entity/Form/ApiDocForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@
*/
class ApiDocForm extends ContentEntityForm {

/**
* {@inheritdoc}
*/
protected function getNewRevisionDefault() {
/* @var \Drupal\apigee_api_catalog\Entity\ApiDoc $entity */
$entity = $this->getEntity();

// Always use the default revision setting.
$new_revision_default = $entity->shouldCreateNewRevision();
return $new_revision_default;
}

/**
* {@inheritdoc}
*/
Expand Down
Loading

0 comments on commit 3b82466

Please sign in to comment.