Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions modules/harvest/harvest.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/**
* @file
* Harvest module file.
*/

use Drupal\Core\Entity\EntityInterface;

/**
* Implements hook_entity_operation().
*/
function harvest_entity_operation(EntityInterface $entity) {
$operations = [];
if ($entity->getEntityTypeId() === 'harvest_plan') {
$operations['run'] = [
'title' => t('Run'),
'url' => $entity->toUrl('run-form'),
'weight' => 0,
];
}
return $operations;
}
10 changes: 6 additions & 4 deletions modules/harvest/harvest.permissions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ administer harvest_plan:
title: 'Administer harvest plans'
restrict access: true
view harvest_plan:
title: 'View harvest plan'
title: 'View harvest plans'
edit harvest_plan:
title: 'Edit harvest plan'
title: 'Edit harvest plans'
delete harvest_plan:
title: 'Delete harvest plan'
title: 'Delete harvest plans'
create harvest_plan:
title: 'Create harvest plan'
title: 'Create harvest plans'
harvest_plan.run:
title: "Run harvest plans"

# These permissions refer to harvest_run entities, not the act of running a
# harvest.
Expand Down
46 changes: 27 additions & 19 deletions modules/harvest/src/ContentAccessControlHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,45 @@

/**
* Defines an access control handler for content entities.
*
* @see \Drupal\harvest\Entity\HarvestPlan
*/
class ContentAccessControlHandler extends EntityAccessControlHandler {

/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account): AccessResult {
return match($operation) {
'view' => AccessResult::allowedIfHasPermissions($account, [
'view ' . $this->entityTypeId,
'administer ' . $this->entityTypeId,
], 'OR'),
'update' => AccessResult::allowedIfHasPermissions($account, [
'edit ' . $this->entityTypeId,
'administer ' . $this->entityTypeId,
], 'OR'),
'delete' => AccessResult::allowedIfHasPermissions($account, [
'delete ' . $this->entityTypeId,
'administer ' . $this->entityTypeId,
], 'OR'),
default => AccessResult::neutral(),
};
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
if ($admin_permission = $this->entityType->getAdminPermission()) {
return AccessResult::allowedIfHasPermission($account, $admin_permission);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof. This is clearly wrong.

}
switch ($operation) {
case 'delete':
case 'view':
case 'run':
return AccessResult::allowedIfHasPermission(
$account,
$this->entityTypeId . '.' . $operation
);

case 'update':
return AccessResult::allowedIfHasPermission(
$account,
'edit ' . $this->entityTypeId
);

default:
return parent::checkAccess($entity, $operation, $account);
}
}

/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL): AccessResult {
return AccessResult::allowedIfHasPermissions($account, [
'create ' . $this->entityTypeId,
'administer ' . $this->entityTypeId,
$this->entityType->getAdminPermission(),
], 'OR');
}

Expand Down
5 changes: 5 additions & 0 deletions modules/harvest/src/Entity/HarvestPlan.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
* "access" = "Drupal\harvest\ContentAccessControlHandler",
* "route_provider" = {
* "html" = "Drupal\harvest\Routing\HarvestDashboardHtmlRouteProvider",
* },
* "form" = {
* "run" = "Drupal\harvest\Form\HarvestPlanRunForm",
* }
* },
* base_table = "harvest_plans",
Expand All @@ -44,6 +47,7 @@
* links = {
* "collection" = "/admin/dkan/harvest",
* "canonical" = "/harvest-plan/{harvest_plan}",
* "run-form" = "/admin/harvest-plan/{harvest_plan}/run",
* },
* internal = TRUE,
* )
Expand All @@ -64,6 +68,7 @@ final class HarvestPlan extends HarvestEntityBase implements HarvestPlanInterfac
* Provides identifier and JSON data base fields.
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
// Get base fields based on annotated config above.
$base_fields = parent::baseFieldDefinitions($entity_type);

// The 'id' field is the unique identifier for each harvest plan row.
Expand Down
75 changes: 75 additions & 0 deletions modules/harvest/src/Form/HarvestPlanRunForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace Drupal\harvest\Form;

use Drupal\Core\Entity\ContentEntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\harvest\Entity\HarvestRunRepository;
use Drupal\harvest\HarvestService;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Confirmation form for when you want to run a harvest plan.
*/
class HarvestPlanRunForm extends ContentEntityConfirmFormBase {

/**
* Harvest service.
*
* @var \Drupal\harvest\HarvestService
*/
protected HarvestService $harvestService;

/**
* Harvest run entity repository.
*
* @var \Drupal\harvest\Entity\HarvestRunRepository
*/
protected HarvestRunRepository $harvestRunRepository;

/**
* {@inheritDoc}
*/
public static function create(ContainerInterface $container) {
$form = parent::create($container);
$form->harvestService = $container->get('dkan.harvest.service');
$form->harvestRunRepository = $container->get('dkan.harvest.storage.harvest_run_repository');

return $form;
}

/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to run the harvest plan %name?', ['%name' => $this->entity->label()]);
}

/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return $this->entity->toUrl('collection');
}

/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Run');
}

/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$plan_id = (string) $this->entity->id();

// @todo Handle exceptions.
$result = $this->harvestService->runHarvest($plan_id);

$harvest_run = $this->harvestRunRepository->loadEntity($plan_id, $result['identifier']);
$form_state->setRedirectUrl($harvest_run->toUrl());
}

}
8 changes: 3 additions & 5 deletions modules/harvest/src/HarvestPlanListBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,20 @@ public function render() {
* {@inheritdoc}
*/
public function buildHeader() {
// Don't call parent::buildHeader() because we don't want operations (yet).
return [
'harvest_link' => $this->t('Harvest ID'),
'extract_status' => $this->t('Extract Status'),
'last_run' => $this->t('Last Run'),
'dataset_count' => $this->t('# of Datasets'),
];
] + parent::buildHeader();
}

/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
/** @var \Drupal\harvest\HarvestPlanInterface $entity */
$harvest_plan_id = $entity->get('id')->getString();
$harvest_plan_id = (string) $entity->id();
$run_entity = NULL;

if ($run_id = $this->harvestRunRepository->getLastHarvestRunId($harvest_plan_id)) {
Expand Down Expand Up @@ -112,8 +111,7 @@ public function buildRow(EntityInterface $entity) {
$row['last_run'] = date('m/d/y H:m:s T', $run_entity->get('timestamp')->value);
$row['dataset_count'] = $interpreter->countProcessed();
}
// Don't call parent::buildRow() because we don't want operations (yet).
return $row;
return $row + parent::buildRow($entity);
}

/**
Expand Down
51 changes: 40 additions & 11 deletions modules/harvest/src/Routing/HarvestDashboardHtmlRouteProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,59 @@
namespace Drupal\harvest\Routing;

use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
use Symfony\Component\Routing\Route;

/**
* Provides HTML routes for entities with administrative pages.
*
* We override for harvest-oriented dashboards so that they have consistent
* permissions handling.
*/
class HarvestDashboardHtmlRouteProvider extends AdminHtmlRouteProvider {
class HarvestDashboardHtmlRouteProvider extends DefaultHtmlRouteProvider {

/**
* {@inheritDoc}
*/
protected function getCollectionRoute(EntityTypeInterface $entity_type) {
$route = NULL;
if ($route = parent::getCollectionRoute($entity_type)) {
$required_permissions = ['dkan.harvest.dashboard'];
if ($permission = $route->getRequirement('_permission')) {
$required_permissions += [$permission];
public function getRoutes(EntityTypeInterface $entity_type) {
// Get the ones Drupal can do.
$collection = parent::getRoutes($entity_type);

$entity_type_id = $entity_type->id();
if ($entity_type_id === 'harvest_plan') {
if ($run_route = $this->getOperationFormRoute($entity_type, 'run')) {
$collection->add('entity.' . $entity_type_id . '.run_form', $run_route);
}
// Use + to specify OR logic in permissions.
$route->setRequirement('_permission', implode('+', $required_permissions));
}
return $route;
return $collection;
}

/**
* Generalized operation route generator.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
* @param string $operation
* Operation name.
*
* @return \Symfony\Component\Routing\Route|null
* The generated route, if available. NULL otherwise.
*/
protected function getOperationFormRoute(EntityTypeInterface $entity_type, string $operation): ?Route {
if ($entity_type->hasLinkTemplate($operation . '-form')) {
$entity_type_id = $entity_type->id();
$route = new Route($entity_type->getLinkTemplate($operation . '-form'));
$route
->addDefaults([
'_entity_form' => $entity_type_id . '.' . $operation,
])
->setOption('parameters', [
$entity_type_id => ['type' => 'entity:' . $entity_type_id],
])
->setRequirement('_entity_access', $entity_type_id . '.' . $operation);
return $route;
}
return NULL;
}

}