Skip to content
Snippets Groups Projects
Commit 04e6c51e authored by dpi's avatar dpi
Browse files

Converted event access page to a form

Moving condition edit button to left side.
Use disabled checkboxes instead of text if default access rules are active for an event.
Improved label copy for checkboxes
Added test coverage for viewing event access page.

Fixed #73
Fixed #105
parent 8580f02a
No related branches found
Tags 3.0.0-beta1
No related merge requests found
<?php
/**
* @file
* Contains \Drupal\rng\Controller\EventController.
*/
namespace Drupal\rng\Controller;
namespace Drupal\rng\Form;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\Core\Action\ActionManager;
use Drupal\Core\Condition\ConditionManager;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Cache\Cache;
use Drupal\rng\EventManagerInterface;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\rng\RNGConditionInterface;
use Drupal\rng\Entity\RuleComponent;
/**
* Controller for events.
* Form to edit event access.
*/
class EventController extends ControllerBase implements ContainerInjectionInterface {
class EventAccessForm extends FormBase {
/**
* The action manager service.
......@@ -51,7 +47,14 @@ class EventController extends ControllerBase implements ContainerInjectionInterf
protected $redirectDestination;
/**
* Constructs a new action form.
* The event entity.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $event;
/**
* Constructs a new EventAccessForm object.
*
* @param \Drupal\Core\Action\ActionManager $actionManager
* The action manager.
......@@ -82,51 +85,79 @@ class EventController extends ControllerBase implements ContainerInjectionInterf
}
/**
* Displays a list of actions which are related to registration access on an
* event.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route.
* @param string $event
* The parameter to find the event entity.
*
* @return array
* A render array.
* {@inheritdoc}
*/
public function getFormId() {
return 'rng_event_access';
}
/**
* {@inheritdoc}
*/
public function listing_access(RouteMatchInterface $route_match, $event) {
$event = $route_match->getParameter($event);
public function buildForm(array $form, FormStateInterface $form_state, $rng_event = NULL) {
$event = clone $rng_event;
$this->event = $event;
$destination = $this->redirectDestination->getAsArray();
$event_meta = $this->eventManager->getMeta($event);
$trigger = 'rng_event.register';
$build = [];
$build['description'] = [
// Allow editing operations on non default rules.
$access_edit_operations = !$event_meta->isDefaultRules($trigger);
$form['description'] = [
'#prefix' => '<p>',
'#markup' => $this->t('The following rules determine who is eligible to register or perform an operation on a registration.<br />Access is granted if all conditions for a rule evaluate as true.'),
'#suffix' => '</p>',
];
$rows = [];
$form['table'] = [
'#type' => 'table',
'#header' => [],
'#rows' => [],
'#empty' => $this->t('No access rules.'),
];
// Header.
$rows[0] = [
['header' => TRUE, 'rowspan' => 2, 'data' => $this->t('Rule')],
['header' => TRUE, 'rowspan' => 2, 'data' => $this->t('Component')],
['header' => TRUE, 'rowspan' => 2, 'data' => $this->t('Scope')],
['header' => TRUE, 'rowspan' => 1, 'data' => $this->t('Operations'), 'colspan' => 4],
$form['table']['header_one'][] = [
'#wrapper_attributes' => [
'header' => TRUE,
'rowspan' => 2,
],
'#plain_text' => $this->t('Rule'),
];
$form['table']['header_one'][] = [
'#wrapper_attributes' => [
'header' => TRUE,
'rowspan' => 2,
'colspan' => $access_edit_operations ? 2 : 1,
],
'#plain_text' => $this->t('Component'),
];
$form['table']['header_one'][] = [
'#wrapper_attributes' => [
'header' => TRUE,
'rowspan' => 2,
],
'#plain_text' => $this->t('Scope'),
];
$form['table']['header_one'][] = [
'#wrapper_attributes' => [
'header' => TRUE,
'rowspan' => 1,
'colspan' => 4,
],
'#plain_text' => $this->t('Operations'),
];
// Add a blank header column if there are edit buttons.
// Edit buttons only available on non default rules.
if (!$event_meta->isDefaultRules($trigger)) {
$rows[0][] = ['header' => TRUE, 'rowspan' => 2, 'data' => ''];
}
$operations = ['create' => $this->t('Create'), 'view' => $this->t('View'), 'update' => $this->t('Update'), 'delete' => $this->t('Delete')];
foreach ($operations as $operation) {
$rows['operations'][] = [
'header' => TRUE,
'data' => $operation,
$form['table']['operations'][] = [
'#wrapper_attributes' => [
'header' => TRUE,
'class' => ['checkbox'],
],
'#plain_text' => $operation,
];
}
......@@ -141,88 +172,187 @@ class EventController extends ControllerBase implements ContainerInjectionInterf
// Conditions.
$k = 0;
$row = [];
$row['rule'] = ['header' => FALSE, 'data' => $this->t('@row.', ['@row' => $i]), 'rowspan' => count($rule->getConditions()) + 1];
// no_striping does not work when using table as form element right now.
$row['#attributes']['no_striping'] = TRUE;
$row['rule'] = [
'#wrapper_attributes' => [
'header' => FALSE,
'rowspan' => count($rule->getConditions()) + 1,
],
'#plain_text' => $this->t('@row.', ['@row' => $i]),
];
foreach ($rule->getConditions() as $condition_storage) {
$k++;
$row[] = ['header' => TRUE, 'data' => $this->t('Condition #@condition', ['@condition' => $k])];
$condition = $condition_storage->createInstance();
$condition_context += array_keys($condition->getContextDefinitions());
$scope_all = (!in_array('registration', $condition_context) || in_array('event', $condition_context));
if (isset($row['rule']['rowspan']) && $scope_all) {
$row['rule']['rowspan']++;
}
$row[] = [
'#wrapper_attributes' => [
'header' => TRUE,
],
'#plain_text' => $this->t('Condition #@condition', ['@condition' => $k]),
];
if ($condition instanceof RNGConditionInterface) {
$supports_create++;
}
$row[] = ['colspan' => 5, 'data' => $condition->summary()];
if (!$event_meta->isDefaultRules($trigger)) {
if ($access_edit_operations) {
$row['condition_operations']['#wrapper_attributes']['header'] = TRUE;
$row['condition_operations']['data'] = ['#type' => 'operations'];
if ($condition_storage->access('edit')) {
$row['condition_operations']['data']['#links']['edit'] = [
'title' => t('Edit'),
'url' => $condition_storage->urlInfo('edit-form'),
'url' => $condition_storage->toUrl('edit-form'),
'query' => $destination,
];
}
}
$rows[] = ['data' => $row, 'no_striping' => TRUE];
$condition = $condition_storage->createInstance();
$condition_context += array_keys($condition->getContextDefinitions());
$scope_all = (!in_array('registration', $condition_context) || in_array('event', $condition_context));
if (isset($row['rule']['#wrapper_attributes']['rowspan']) && $scope_all) {
$row['rule']['#wrapper_attributes']['rowspan']++;
}
if ($condition instanceof RNGConditionInterface) {
$supports_create++;
}
$row[] = [
'#wrapper_attributes' => [
'colspan' => 5,
],
'#markup' => $condition->summary(),
];
$form['table'][] = $row;
$row = [];
}
// Actions.
foreach ($rule->getActions() as $action_storage) {
/** @var \Drupal\rng\RuleComponentInterface $action_storage */
$row = [];
$row[] = ['header' => TRUE, 'data' => $this->t('Grants operations'), 'rowspan' => $scope_all ? 2 : 1];
$row[] = [
'#wrapper_attributes' => [
'header' => TRUE,
'rowspan' => $scope_all ? 2 : 1,
'colspan' => $access_edit_operations ? 2 : 1,
],
'#plain_text' => $this->t('Grants operations'),
];
// Scope: warn user actions apply to all registrations.
$row[]['data'] = $scope_all ? $this->t('All registrations.') : $this->t('Single registration');
$row[] = [
'#plain_text' => $scope_all ? $this->t('All registrations.') : $this->t('Single registration'),
];
// Operations granted.
$config = $action_storage->getConfiguration();
foreach ($operations as $op => $t) {
$message = !empty($config['operations'][$op]) ? $t : '-';
$row['operation_' . $op] = ['data' => ($op == 'create' && ($supports_create != count($rule->getConditions()))) ? $this->t('<em>N/A</em>') : $message];
}
foreach ($operations as $op => $operation) {
$cell = [
'#wrapper_attributes' => [
'class' => ['checkbox'],
],
];
if (!$event_meta->isDefaultRules($trigger)) {
$links = [];
if ($action_storage->access('edit')) {
$links['edit'] = [
'title' => t('Edit'),
'url' => $action_storage->urlInfo('edit-form'),
'query' => $destination,
if (($op == 'create' && ($supports_create != count($rule->getConditions())))) {
$cell['#markup'] = $this->t('<em>N/A</em>');
}
else {
$cell['component_id'] = [
'#type' => 'value',
'#value' => $action_storage->id(),
];
$cell['operation'] = [
'#type' => 'value',
'#value' => $op,
];
$cell['enabled'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow registrant to @operation registrations if all conditions pass on this rule.', [
'@operation' => $op,
]),
'#title_display' => 'invisible',
'#default_value' => !empty($config['operations'][$op]),
'#disabled' => !$access_edit_operations,
];
}
$row[] = [
'data' => ['#type' => 'operations', '#links' => $links],
'rowspan' => $scope_all ? 2 : 1
];
$row['operation_' . $op] = $cell;
}
$rows[] = $row;
$form['table'][] = $row;
if ($scope_all) {
$rows[] = [
[
'data' => $this->t('<strong>Warning:</strong> selecting view, update, or delete grants access to any registration on this event.'),
'colspan' => 5,
]
$form['table'][][] = [
'#wrapper_attributes' => [
'colspan' => 5,
],
'#markup' => $this->t('<strong>Warning:</strong> selecting view, update, or delete grants access to any registration on this event.'),
];
}
}
}
$build[] = [
'#type' => 'table',
'#header' => [],
'#rows' => $rows,
'#empty' => $this->t('No access rules.'),
];
if ($access_edit_operations) {
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => t('Save'),
'#button_type' => 'primary',
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$event = $this->event;
$event_meta = $this->eventManager->getMeta($event);
$trigger = 'rng_event.register';
if ($event_meta->isDefaultRules($trigger)) {
$form_state->setError($form, $this->t('This event is using default rules.'));
return;
}
// Component_id => [operation => enabled?, ...]
$component_operations = [];
foreach ($form_state->getValue('table') as $row) {
foreach ($row as $cell) {
$enabled = !empty($cell['enabled']);
$operation = $cell['operation'];
$component_id = $cell['component_id'];
$component_operations[$component_id][$operation] = $enabled;
}
}
$form_state->setValue('component_operations', $component_operations);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$component_operations = $form_state->getValue('component_operations');
foreach ($component_operations as $component_id => $operations) {
$component = RuleComponent::load($component_id);
$configuration = $component->getConfiguration();
foreach ($operations as $operation => $enabled) {
$configuration['operations'][$operation] = $enabled;
}
$component
->setConfiguration($configuration)
->save();
}
return $build;
Cache::invalidateTags($this->event->getCacheTagsToInvalidate());
drupal_set_message($this->t('Updated access operations.'));
}
}
......@@ -79,11 +79,11 @@ class RouteSubscriber extends RouteSubscriberBase {
// Access.
$route = new Route(
$canonical_path . '/event/access',
array(
'_controller' => '\Drupal\rng\Controller\EventController::listing_access',
[
'_form' => '\Drupal\rng\Form\EventAccessForm',
'_title' => 'Access',
'event' => $entity_type,
),
],
$manage_requirements,
$options
);
......
......@@ -37,6 +37,8 @@ class EventSettingsTest extends RNGSiteTestBase {
$event_types[0] = $this->event_type;
$event_types[1] = $this->createEventType($bundle[2]);
\Drupal::service('router.builder')->rebuildIfNeeded();
$account = $this->drupalCreateUser([
'edit own ' . $bundle[0]->id() . ' content',
'edit own ' . $bundle[1]->id() . ' content',
......
......@@ -48,7 +48,7 @@ trait RNGTestTrait {
* An event type config.
*/
protected function createEventType($entity_type_id, $bundle, $values = []) {
$event_type = EventType::create([
$event_type = EventType::create($values + [
'label' => 'Event Type A',
'entity_type' => $entity_type_id,
'bundle' => $bundle,
......
<?php
namespace Drupal\Tests\rng\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\rng\Tests\RNGTestTrait;
/**
* Base test class for functional browser tests.
*/
abstract class RngBrowserTestBase extends BrowserTestBase {
use RNGTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['rng', 'user', 'field', 'dynamic_entity_reference', 'unlimited_number', 'courier', 'text'];
/**
* The RNG event manager.
*
* @var \Drupal\rng\EventManagerInterface
*/
protected $eventManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->eventManager = $this->container->get('rng.event_manager');
}
}
<?php
namespace Drupal\Tests\rng\Functional;
use Drupal\Core\Url;
use Drupal\rng\Form\EventTypeForm;
/**
* Tests manage event access page.
*
* @group rng
*/
class RngEventAccessWebTest extends RngBrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['entity_test', 'block'];
/**
* A registration type for testing.
*
* @var \Drupal\rng\RegistrationTypeInterface
*/
protected $registrationType;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->registrationType = $this->createRegistrationType();
$this->createEventType('entity_test', 'entity_test');
EventTypeForm::createDefaultRules('entity_test', 'entity_test');
$this->container->get('router.builder')->rebuildIfNeeded();
$this->container->get('plugin.manager.menu.local_action')->clearCachedDefinitions();
$this->drupalPlaceBlock('local_actions_block');
}
/**
* Test event access page when using site defaults.
*/
public function testEventAccessSiteDefaults() {
$user1 = $this->drupalCreateUser([
'view test entity',
'administer entity_test content',
]);
$this->drupalLogin($user1);
$event_meta = $this->createEvent(['user_id' => $user1->id()]);
$this->drupalGet(Url::fromRoute('rng.event.entity_test.access', [
'entity_test' => $event_meta->getEvent()->id(),
]));
// Reset access rules button.
$reset_link = Url::fromRoute('rng.event.entity_test.access.reset', [
'entity_test' => $event_meta->getEvent()->id(),
]);
$this->assertSession()->linkExists(t('Customize access rules'));
$this->assertSession()->linkByHrefExists($reset_link->toString());
// Check if one of the checkboxes is disabled.
$field_name = 'table[6][operation_create][enabled]';
$this->assertSession()->fieldExists($field_name);
$input = $this->xpath('//input[@name="' . $field_name . '" and @disabled="disabled"]');
$this->assertTrue(count($input) === 1, 'The create checkbox is disabled.');
}
/**
* Test event access page when using custom rules.
*/
public function testEventAccessCustomized() {
$user1 = $this->drupalCreateUser([
'view test entity',
'administer entity_test content',
]);
$this->drupalLogin($user1);
$event_meta = $this->createEvent(['user_id' => $user1->id()]);
$event_meta->addDefaultAccess();
$this->drupalGet(Url::fromRoute('rng.event.entity_test.access', [
'entity_test' => $event_meta->getEvent()->id(),
]));
// Reset access rules button.
$reset_link = Url::fromRoute('rng.event.entity_test.access.reset', [
'entity_test' => $event_meta->getEvent()->id(),
]);
$this->assertSession()->linkExists(t('Reset access rules to site default'));
$this->assertSession()->linkByHrefExists($reset_link->toString());
// Check if one of the checkboxes is enabled.
$field_name = 'table[6][operation_create][enabled]';
$this->assertSession()->fieldExists($field_name);
$input = $this->xpath('//input[@name="' . $field_name . '" and @disabled="disabled"]');
$this->assertTrue(count($input) === 0, 'The create checkbox is not disabled.');
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment