diff --git a/modules/harvest/harvest.module b/modules/harvest/harvest.module new file mode 100644 index 0000000000..7748de09fe --- /dev/null +++ b/modules/harvest/harvest.module @@ -0,0 +1,23 @@ +getEntityTypeId() === 'harvest_plan') { + $operations['run'] = [ + 'title' => t('Run'), + 'url' => $entity->toUrl('run-form'), + 'weight' => 0, + ]; + } + return $operations; +} diff --git a/modules/harvest/harvest.permissions.yml b/modules/harvest/harvest.permissions.yml index 022d1d6a1d..0965df122b 100644 --- a/modules/harvest/harvest.permissions.yml +++ b/modules/harvest/harvest.permissions.yml @@ -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. diff --git a/modules/harvest/src/ContentAccessControlHandler.php b/modules/harvest/src/ContentAccessControlHandler.php index ba07d42ce3..af759b2fc8 100644 --- a/modules/harvest/src/ContentAccessControlHandler.php +++ b/modules/harvest/src/ContentAccessControlHandler.php @@ -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); + } + 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'); } diff --git a/modules/harvest/src/Entity/HarvestPlan.php b/modules/harvest/src/Entity/HarvestPlan.php index 4f7d2be4c6..e2ecb35d0e 100644 --- a/modules/harvest/src/Entity/HarvestPlan.php +++ b/modules/harvest/src/Entity/HarvestPlan.php @@ -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", @@ -44,6 +47,7 @@ * links = { * "collection" = "/admin/dkan/harvest", * "canonical" = "/harvest-plan/{harvest_plan}", + * "run-form" = "/admin/harvest-plan/{harvest_plan}/run", * }, * internal = TRUE, * ) @@ -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. diff --git a/modules/harvest/src/Form/HarvestPlanRunForm.php b/modules/harvest/src/Form/HarvestPlanRunForm.php new file mode 100644 index 0000000000..3128a64bbe --- /dev/null +++ b/modules/harvest/src/Form/HarvestPlanRunForm.php @@ -0,0 +1,75 @@ +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()); + } + +} diff --git a/modules/harvest/src/HarvestPlanListBuilder.php b/modules/harvest/src/HarvestPlanListBuilder.php index ba86e13ae6..88971d77e2 100644 --- a/modules/harvest/src/HarvestPlanListBuilder.php +++ b/modules/harvest/src/HarvestPlanListBuilder.php @@ -68,13 +68,12 @@ 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(); } /** @@ -82,7 +81,7 @@ public function buildHeader() { */ 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)) { @@ -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); } /** diff --git a/modules/harvest/src/Routing/HarvestDashboardHtmlRouteProvider.php b/modules/harvest/src/Routing/HarvestDashboardHtmlRouteProvider.php index 15a7d46169..cab6232214 100644 --- a/modules/harvest/src/Routing/HarvestDashboardHtmlRouteProvider.php +++ b/modules/harvest/src/Routing/HarvestDashboardHtmlRouteProvider.php @@ -3,7 +3,8 @@ 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. @@ -11,22 +12,50 @@ * 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; } }