diff --git a/config/install/rabbit_hole.behavior_settings.default_bundle.yml b/config/install/rabbit_hole.behavior_settings.default_bundle.yml index 8bc5ad6..0c4bf0b 100644 --- a/config/install/rabbit_hole.behavior_settings.default_bundle.yml +++ b/config/install/rabbit_hole.behavior_settings.default_bundle.yml @@ -4,3 +4,4 @@ action: "display_page" allow_override: 1 # N/A redirect_code: 0 +redirect_path: '' diff --git a/modules/rh_node/src/Tests/InvocationTest.php b/modules/rh_node/src/Tests/InvocationTest.php new file mode 100644 index 0000000..1a20b23 --- /dev/null +++ b/modules/rh_node/src/Tests/InvocationTest.php @@ -0,0 +1,172 @@ +behaviorSettingsManager = $this->container + ->get('rabbit_hole.behavior_settings_manager'); + } + + /** + * Test that a fresh node with a fresh content type takes the default action + * (displays the page) + */ + public function testNodeDefaults() { + $type = $this->createTestNodeType(); + $node = $this->createTestNodeOfType($type->id()); + $this->drupalGet(self::NODE_BASE_PATH . $node->id()); + $this->assertResponse(200); + } + + /** + * Test that a node with action not set or set to bundle_default will default + * to the bundle action + */ + public function testDefaultToBundle() { + $type = $this->createTestNodeType('access_denied'); + $node = $this->createTestNodeOfType($type->id()); + $this->drupalGet(self::NODE_BASE_PATH . $node->id()); + $this->assertResponse(403); + + $node2 = $this->createTestNodeOfType($type->id(), 'bundle_default'); + $this->drupalGet(self::NODE_BASE_PATH . $node2->id()); + $this->assertResponse(403); + } + + /** + * Test that a node set to access_denied overrides the bundle and returns a + * 403 response + */ + public function testAccessDenied() { + $type = $this->createTestNodeType(); + $node = $this->createTestNodeOfType($type->id(), 'access_denied'); + $this->drupalGet(self::NODE_BASE_PATH . $node->id()); + $this->assertResponse(403); + } + + /** + * Test that a node set to display_page overrides the bundle and returns a + * 200 response + */ + public function testDisplayPage() { + $type = $this->createTestNodeType('access_denied'); + $node = $this->createTestNodeOfType($type->id(), 'display_page'); + $this->drupalGet(self::NODE_BASE_PATH . $node->id()); + $this->assertResponse(200); + } + + public function testUrlRedirects() { + $type = $this->createTestNodeType('access_denied'); + + $this->testUrlRedirect(301, $type); + $this->testUrlRedirect(302, $type); + $this->testUrlRedirect(303, $type); + // $this->testUrlRedirect(304, $type); + $this->testUrlRedirect(305, $type); + $this->testUrlRedirect(307, $type); + } + + /** + * Test URL redirects with tokens + * @todo + */ + public function testTokenizedUrlRedirect() {} + + /** + * Test redirects that use PHP code + * @todo + */ + public function testCodeRedirect() {} + + /** + * Test that a node set to page_not_found overrides the bundle and returns a + * 404 response + */ + public function testPageNotFound() { + $type = $this->createTestNodeType(); + $node = $this->createTestNodeOfType($type->id(), 'page_not_found'); + $this->drupalGet(self::NODE_BASE_PATH . $node->id()); + $this->assertResponse(404); + } + + private function createTestNodeType($action = NULL) { + $node_type = NodeType::create( + array( + 'type' => self::TEST_CONTENT_TYPE_ID, + 'name' => self::TEST_CONTENT_TYPE_ID, + ) + ); + $node_type->save(); + if (isset($action)) { + $this->behaviorSettingsManager->saveBehaviorSettings( + array('action' => $action), 'node_type', $node_type->id()); + } + return $node_type; + } + + private function createTestNodeOfType($node_type_id = self::TEST_CONTENT_TYPE_ID, $action = NULL) { + $node = Node::create( + array( + 'nid' => NULL, + 'type' => $node_type_id, + 'title' => 'Test Behavior Settings Node', + ) + ); + if (isset($action)) { + $node->set('rh_action', $action); + } + $node->save(); + return $node; + } + + /** + * Test some simple URL redirects + */ + private function testUrlRedirect($redirect_code, $type) { + global $base_root; + + $target_node = $this->createTestNodeOfType($type->id(), 'display_page'); + $destination_path = self::NODE_BASE_PATH . $target_node->id(); + + $node = $this->createTestNodeOfType($type->id(), 'page_redirect'); + $node->set('rh_redirect', $base_root . $destination_path); + $node->set('rh_redirect_response', $redirect_code); + $node->save(); + $this->drupalGet(self::NODE_BASE_PATH . $node->id()); + // $this->assertResponse($redirect_code); + $this->assertUrl($base_root . $destination_path); + } +} diff --git a/rabbit_hole.module b/rabbit_hole.module index c922125..fa3398b 100644 --- a/rabbit_hole.module +++ b/rabbit_hole.module @@ -6,7 +6,6 @@ */ use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\Core\Field\BaseFieldDefinition; /** * Implements hook_help(). @@ -33,17 +32,8 @@ function rabbit_hole_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterf // TODO: Generate array of entity types from plugins // if (entity_type->id() in_array($entity_types)) { if ($entity_type->id() == 'node') { - $fields = array(); - $fields['rh_action'] = BaseFieldDefinition::create('string') - ->setLabel(t('Rabbit Hole action')) - ->setDescription(t('Specifies which action that Rabbit Hole should take.')); - $fields['rh_redirect'] = BaseFieldDefinition::create('string') - ->setLabel(t('Rabbit Hole redirect path')) - ->setDescription(t('The path to where the user should get redirected to.')); - $fields['rh_redirect_response'] = BaseFieldDefinition::create('integer') - ->setLabel(t('Rabbit Hole redirect response code')) - ->setDescription(t('Specifies the HTTP response code that should be used when perform a redirect.')); - return $fields; + return \Drupal::service('rabbit_hole.entity_extender') + ->getGeneralExtraFields(); } } diff --git a/rabbit_hole.services.yml b/rabbit_hole.services.yml index 1860ff0..0a87a9e 100644 --- a/rabbit_hole.services.yml +++ b/rabbit_hole.services.yml @@ -13,3 +13,20 @@ services: - '@plugin.manager.rabbit_hole_behavior_plugin' - '@rabbit_hole.behavior_settings_manager' - "@string_translation" + rabbit_hole.behavior_invoker: + class: Drupal\rabbit_hole\BehaviorInvoker + arguments: + - "@rabbit_hole.behavior_settings_manager" + - "@plugin.manager.rabbit_hole_behavior_plugin" + - "@rabbit_hole.entity_extender" + + rabbit_hole.rabbit_hole_subscriber: + class: Drupal\rabbit_hole\EventSubscriber\RabbitHoleSubscriber + arguments: ["@rabbit_hole.behavior_invoker"] + tags: + - { name: event_subscriber } + + rabbit_hole.entity_extender: + class: Drupal\rabbit_hole\EntityExtender + arguments: ["@plugin.manager.rabbit_hole_behavior_plugin"] + diff --git a/src/BehaviorInvoker.php b/src/BehaviorInvoker.php new file mode 100644 index 0000000..d51b665 --- /dev/null +++ b/src/BehaviorInvoker.php @@ -0,0 +1,141 @@ +rhBehaviorSettingsManager = $rabbit_hole_behavior_settings_manager; + $this->rhBehaviorPluginManager = $plugin_manager_rabbit_hole_behavior_plugin; + $this->rhEntityExtender = $entity_extender; + } + + /** + * Invoke a rabbit hole behavior based on an entity's configuration + * @note This assumes the entity is configured for use with Rabbit Hole - if + * you pass an entity to this method and it does not have a rabbit hole + * plugin it will use the defaults! + * @note This method can be triggered with a response if any plugins need + * it but this actually has no effect right now. Left it in because it + * might be useful. + */ + public function processEntity($entity, Response $current_response = NULL) { + + $values = $this->getRabbitHoleValuesForEntity($entity); + $plugin = $this->rhBehaviorPluginManager + ->createInstance($values['rh_action'], $values); + + $resp_use = $plugin->usesResponse(); + $response_required = $resp_use == RabbitHoleBehaviorPluginInterface::USES_RESPONSE_ALWAYS; + $response_allowed = $resp_use == $response_required + || $resp_use == RabbitHoleBehaviorPluginInterface::USES_RESPONSE_SOMETIMES; + + + // Most plugins never make use of the response and only run when it's not + // provided (i.e. on a request event). + if ((!$response_allowed && $current_response == NULL) + // Some plugins may or may not make use of the response so they'll run in + // both cases and work out the logic of when to return NULL internally. + || $response_allowed + // Though none exist at the time of this writing, some plugins could + // require a response so that case is handled. + || $response_required && $current_response != NULL) { + + return $plugin->performAction($entity, $current_response); + } + // All other cases return NULL, meaning the response is unchanged + else { + return NULL; + } + } + + /** + * @todo Load this from plugins + */ + public function getPossibleEntityKeys() { + return array( + 'node' + ); + } + + /** + * Return an entity's rabbit hole configuration or, failing that, the default + * configuration for the bundle (which itself will call the base default + * configuration if necessary) + * @return array An array of values from the entity's fields matching the + * base properties added by rabbit hole + */ + private function getRabbitHoleValuesForEntity(ContentEntityBase $entity) { + $field_keys = array_keys($this->rhEntityExtender->getGeneralExtraFields()); + $values = array(); + // We trigger the default bundle action under the following circumstances: + // Entity does not have rh_action field + $trigger_default_bundle_action = !$entity->hasField('rh_action') + // Entity has rh_action field but it's null (hasn't been set) + || $entity->get('rh_action')->value == NULL + // Entity has been explicitly set to use the default bundle action + || $entity->get('rh_action')->value == 'bundle_default'; + + if ($trigger_default_bundle_action) { + $config = $this->rhBehaviorSettingsManager->loadBehaviorSettingsAsConfig( + $entity->getEntityType()->getBundleEntityType(), + $entity->bundle()); + + foreach ($field_keys as $field_key) { + $config_field_key = substr($field_key, 3); + $values[$field_key] = $config->get($config_field_key); + } + } + else { + foreach ($field_keys as $field_key) { + if ($entity->hasField($field_key)) { + $values[$field_key] = $entity->{$field_key}->value; + } + } + } + return $values; + } +} diff --git a/src/BehaviorInvokerInterface.php b/src/BehaviorInvokerInterface.php new file mode 100644 index 0000000..f899850 --- /dev/null +++ b/src/BehaviorInvokerInterface.php @@ -0,0 +1,18 @@ +config_factory->get( 'rabbit_hole.behavior_settings.' - . $this->generateBehaviorSettingsFullId($entity_type_id, $entity_id, - $is_bundle - ) - ); + . $this->generateBehaviorSettingsFullId($entity_type_id, $entity_id)); if (!$actual->isNew()) { return $actual; } @@ -99,7 +96,7 @@ class BehaviorSettingsManager implements BehaviorSettingsManagerInterface { * @return string The full id appropriate for a BehaviorSettings config entity */ private function generateBehaviorSettingsFullId($entity_type_id, - $entity_id = '', $is_bundle = FALSE) { + $entity_id = '') { return $entity_type_id . (isset($entity_id) ? '_' . $entity_id : ''); } } diff --git a/src/BehaviorSettingsManagerInterface.php b/src/BehaviorSettingsManagerInterface.php index ba91d9c..16c6d3e 100644 --- a/src/BehaviorSettingsManagerInterface.php +++ b/src/BehaviorSettingsManagerInterface.php @@ -19,30 +19,25 @@ interface BehaviorSettingsManagerInterface { * @param array settings The settings for the BehaviorSettings entity * @param string $entity_type_id The entity type (e.g. node) as a string * @param string $entity_id The entity ID as a string - * @param boolean $is_bundle Whether the entity is a bundle */ - public function saveBehaviorSettings($settings, $entity_type_id, $entity_id, - $is_bundle = FALSE); + public function saveBehaviorSettings($settings, $entity_type_id, $entity_id); /** * Load rabbit hole behaviour settings appropriate to the given config or * default settings if not available * @param string $entity_type_label The entity type (e.g. node) as a string * @param string $entity_id The entity ID as a string - * @param boolean $is_bundle Whether the entity is a bundle * @return \Drupal\Core\Config\ImmutableConfig The BehaviorSettings Config object */ - public function loadBehaviorSettingsAsConfig($entity_type_label, $entity_id, - $is_bundle = FALSE); + public function loadBehaviorSettingsAsConfig($entity_type_label, $entity_id); /** * Load editable rabbit hole behaviour settings appropriate to the given config * or NULL if not available * @param string $entity_type_label The entity type (e.g. node) as a string * @param string $entity_id The entity ID as a string - * @param boolean $is_bundle Whether the entity is a bundle * @return \Drupal\Core\Config\ImmutableConfig|null The BehaviorSettings Config object */ public function loadBehaviorSettingsAsEditableConfig($entity_type_label, - $entity_id, $is_bundle = FALSE); + $entity_id); } diff --git a/src/EntityExtender.php b/src/EntityExtender.php new file mode 100644 index 0000000..52d81c3 --- /dev/null +++ b/src/EntityExtender.php @@ -0,0 +1,51 @@ +rhBehaviorPluginManager = $plugin_manager_rabbit_hole_behavior_plugin; + } + + /** + * {@inheritdoc} + */ + public function getGeneralExtraFields() { + $fields = array(); + $fields['rh_action'] = BaseFieldDefinition::create('string') + ->setLabel($this->t('Rabbit Hole action')) + ->setDescription($this->t('Specifies which action that Rabbit Hole should take.')); + foreach ($this->rhBehaviorPluginManager->getDefinitions() as $id => $def) { + $this->rhBehaviorPluginManager + ->createInstance($id) + ->alterExtraFields($fields); + } + return $fields; + } +} diff --git a/src/EntityExtenderInterface.php b/src/EntityExtenderInterface.php new file mode 100644 index 0000000..f40e825 --- /dev/null +++ b/src/EntityExtenderInterface.php @@ -0,0 +1,20 @@ +rabbitHoleBehaviorInvoker = $rabbit_hole_behavior_invoker; + } + + /** + * {@inheritdoc} + */ + static function getSubscribedEvents() { + $events['kernel.request'] = ['onRequest']; + $events['kernel.response'] = ['onResponse']; + return $events; + } + + /** + * This method is called whenever the kernel.request event is + * dispatched. It invokes a rabbit hole behavior on an entity in + * the request if applicable. + * + * @param GetResponseEvent $event + */ + public function onRequest(Event $event) { + return $this->processEvent($event); + } + + /** + * This method is called whenever a kernel.response event is dispatched. Like + * the onRequest event, it invokes a rabbit hole behavior on an entity in + * the request if possible. Unlike the onRequest event, it also passes in a + * response. + */ + public function onResponse(Event $event) { + return $this->processEvent($event); + } + + /** + * Process events generically invoking rabbit hole behaviors if necessary + */ + private function processEvent(Event $event) { + // We won't go ahead if we have an entity form (i.e. we're adding/editing + // an entity) + if ($event->getRequest()->get('_entity_form') == NULL) { + // We check for all of our known entity keys that work with rabbit hole + // and invoke rabbit hole behavior on the first one we find (which + // should also be the only one) + $entity_keys = $this->rabbitHoleBehaviorInvoker->getPossibleEntityKeys(); + foreach ($entity_keys as $ekey) { + $entity = $event->getRequest()->get($ekey); + if (isset($entity)) { + $new_response = $this->rabbitHoleBehaviorInvoker + ->processEntity($entity, $event->getResponse()); + if (isset($new_response)) { + $event->setResponse($new_response); + } + break; + } + } + } + } +} diff --git a/src/Exception/InvalidRedirectResponseException.php b/src/Exception/InvalidRedirectResponseException.php new file mode 100644 index 0000000..95e2e0d --- /dev/null +++ b/src/Exception/InvalidRedirectResponseException.php @@ -0,0 +1,10 @@ +rhBehaviorSettingsManager @@ -89,8 +88,6 @@ class FormManglerService { $entity->id()); $action = $bundle_settings->get('action'); - $redirect_path = $bundle_settings->get('redirect_path'); - $redirect_code = $bundle_settings->get('redirect_code'); } else { $bundle_settings = $this->rhBehaviorSettingsManager @@ -199,6 +196,9 @@ class FormManglerService { '#attributes' => array('class' => array('rabbit-hole-action-setting')), ); + $this->populateExtraBehaviorSections($form, NULL, NULL, $entity, + $entity_is_bundle, $bundle_settings); + /** * @todo Add redirect settings */ @@ -364,6 +364,24 @@ class FormManglerService { return $action_options; } + /** + * Add additional fields to the form based on behaviors + * @param array &$form The form + * @param type $form_state The form state + * @param type $form_id The form ID + */ + protected function populateExtraBehaviorSections(&$form, $form_state, + $form_id, Entity $entity, $entity_is_bundle = FALSE, + ImmutableConfig $bundle_settings = NULL) { + + foreach ($this->rhBehaviorPluginManager->getDefinitions() as $id => $def) { + $this->rhBehaviorPluginManager + ->createInstance($id) + ->settingsForm($form['rabbit_hole'], $form_state, $form_id, $entity, + $entity_is_bundle, $bundle_settings); + } + } + protected function isEntityBundle($entity) { return is_subclass_of($entity, 'Drupal\Core\Config\Entity\ConfigEntityBundleBase'); diff --git a/src/Plugin/RabbitHoleBehaviorPlugin/AccessDenied.php b/src/Plugin/RabbitHoleBehaviorPlugin/AccessDenied.php index b8c0890..e06dc34 100644 --- a/src/Plugin/RabbitHoleBehaviorPlugin/AccessDenied.php +++ b/src/Plugin/RabbitHoleBehaviorPlugin/AccessDenied.php @@ -7,6 +7,8 @@ namespace Drupal\rabbit_hole\Plugin\RabbitHoleBehaviorPlugin; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpFoundation\Response; +use Drupal\Core\Entity\Entity; use Drupal\rabbit_hole\Plugin\RabbitHoleBehaviorPluginBase; /** @@ -22,7 +24,7 @@ class AccessDenied extends RabbitHoleBehaviorPluginBase { /** * {@inheritdoc} */ - public function performAction() { + public function performAction(Entity $entity, Response $current_response = NULL) { throw new AccessDeniedHttpException(); } diff --git a/src/Plugin/RabbitHoleBehaviorPlugin/PageNotFound.php b/src/Plugin/RabbitHoleBehaviorPlugin/PageNotFound.php index 30d96b6..6a74c82 100644 --- a/src/Plugin/RabbitHoleBehaviorPlugin/PageNotFound.php +++ b/src/Plugin/RabbitHoleBehaviorPlugin/PageNotFound.php @@ -7,7 +7,9 @@ namespace Drupal\rabbit_hole\Plugin\RabbitHoleBehaviorPlugin; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Drupal\Core\Entity\Entity; use Drupal\rabbit_hole\Plugin\RabbitHoleBehaviorPluginBase; +use Symfony\Component\HttpFoundation\Response; /** * Denies access to a page. @@ -22,7 +24,7 @@ class PageNotFound extends RabbitHoleBehaviorPluginBase { /** * {@inheritdoc} */ - public function performAction() { + public function performAction(Entity $entity, Response $current_response = NULL) { throw new NotFoundHttpException(); } diff --git a/src/Plugin/RabbitHoleBehaviorPlugin/PageRedirect.php b/src/Plugin/RabbitHoleBehaviorPlugin/PageRedirect.php index a31b280..667c4a8 100644 --- a/src/Plugin/RabbitHoleBehaviorPlugin/PageRedirect.php +++ b/src/Plugin/RabbitHoleBehaviorPlugin/PageRedirect.php @@ -6,9 +6,17 @@ namespace Drupal\rabbit_hole\Plugin\RabbitHoleBehaviorPlugin; +use Drupal\Core\Url; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Link; +use Drupal\Core\Entity\Entity; +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Config\ImmutableConfig; use Drupal\rabbit_hole\Plugin\RabbitHoleBehaviorPluginBase; +use Drupal\rabbit_hole\Exception\InvalidRedirectResponseException; +use Drupal\rabbit_hole\Plugin\RabbitHoleBehaviorPluginInterface; use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Response; /** * Redirects to another page. @@ -21,6 +29,16 @@ use Symfony\Component\HttpFoundation\RedirectResponse; class PageRedirect extends RabbitHoleBehaviorPluginBase { use StringTranslationTrait; + const RABBIT_HOLE_PAGE_REDIRECT_DEFAULT = ''; + const RABBIT_HOLE_PAGE_REDIRECT_RESPONSE_DEFAULT = 301; + + const REDIRECT_MOVED_PERMANENTLY = 301; + const REDIRECT_FOUND = 302; + const REDIRECT_SEE_OTHER = 303; + const REDIRECT_NOT_MODIFIED = 304; + const REDIRECT_USE_PROXY = 305; + const REDIRECT_TEMPORARY_REDIRECT = 307; + /** * The redirect path. * @@ -45,32 +63,172 @@ class PageRedirect extends RabbitHoleBehaviorPluginBase { /** * {@inheritdoc} */ - public function performAction() { - return new RedirectResponse($this->path, $this->code); + public function performAction(Entity $entity, Response $current_response = NULL) { + // return new RedirectResponse($this->path, $this->code); + + $target = $entity->get('rh_redirect')->value; + if (substr($target, 0, 4) == 'get('rh_redirect_response')->value) { + case self::REDIRECT_MOVED_PERMANENTLY: + case self::REDIRECT_FOUND: + case self::REDIRECT_SEE_OTHER: + case self::REDIRECT_TEMPORARY_REDIRECT: + if ($current_response === NULL) { + return new RedirectResponse($target, + $entity->get('rh_redirect_response')->value); + } + else { + // If a response already exists we don't need to do anything with it + return $current_response; + } + // TODO: I don't think this is the correct way to handle a 304 response + case self::REDIRECT_NOT_MODIFIED: + if ($current_response === NULL) { + $not_modified_response = new Response(); + $not_modified_response->setStatusCode(self::REDIRECT_NOT_MODIFIED); + $not_modified_response->headers->set('Location', $target); + return $not_modified_response; + } + else { + // If a response already exists we don't need to do anything with it + return $current_response; + } + // TODO: I have no idea if this is actually the correct way to handle a + // 305 response in Symfony/D8. Documentation on it seems a bit sparse. + case self::REDIRECT_USE_PROXY: + if ($current_response === NULL) { + $use_proxy_response = new Response(); + $use_proxy_response->setStatusCode(self::REDIRECT_USE_PROXY); + $use_proxy_response->headers->set('Location', $target); + return $use_proxy_response; + } + else { + // If a response already exists we don't need to do anything with it + return $current_response; + } + default: + throw new InvalidRedirectResponseException(); + } } /** * {@inheritdoc} */ - public function settingsForm(&$form, &$form_state, $form_id) { - $form['redirect'] = [ - '#type' => 'details', - '#title' => $this->t('Redirect settings'), - ]; - $form['redirect']['redirect_path'] = [ - '#type' => 'text', - '#title' => $this->t('Redirect path'), - '#description' => $this->t('Enter the relative path or the full URL that the user should get redirected to. Query strings and fragments are supported, such as %example_url. You may enter tokens in this field.', - ['%example_url' => 'http://www.example.com/?query=value#fragment']), - '#default_value' => $this->path, - ]; - $form['redirect']['redirect_response'] = [ + public function settingsForm(&$form, &$form_state, $form_id, Entity $entity = NULL, + $entity_is_bundle = FALSE, ImmutableConfig $bundle_settings = NULL) { + + $redirect_path = NULL; + $redirect_code = NULL; + + if (isset($entity)) { + if ($entity_is_bundle) { + $redirect_path = $bundle_settings->get('redirect_path'); + $redirect_code = $bundle_settings->get('redirect_code'); + } + else { + $redirect_path = isset($entity->rh_redirect->value) + ? $entity->rh_redirect->value + : self::RABBIT_HOLE_PAGE_REDIRECT_DEFAULT; + $redirect_code = isset($entity->rh_redirect_code->value) + ? $entity->rh_redirect_code->value + : self::RABBIT_HOLE_PAGE_REDIRECT_RESPONSE_DEFAULT; + } + } + else { + $redirect_path = NULL; + $redirect_code = NULL; + } + + $form['rabbit_hole']['redirect'] = array( + '#type' => 'fieldset', + '#title' => t('Redirect settings'), + '#attributes' => array('class' => array('rabbit-hole-redirect-options')), + '#states' => array( + 'visible' => array( + ':input[name="rh_action"]' => array('value' => $this->getPluginId()), + ), + ), + ); + + // Get the default value for the redirect path. + // $redirect_default_value = isset($entity) ? rabbit_hole_get_redirect_entity($entity_type, $entity) : (!empty($bundle) ? rabbit_hole_get_redirect_bundle($entity_type, $bundle) : RABBIT_HOLE_PAGE_REDIRECT_DEFAULT); + + // Build the descriptive text. Add some help text for PHP, if the user has the + // permission to use PHP for evaluation. + $description = array(); + $description[] = t('Enter the relative path or the full URL that the user should get redirected to. Query strings and fragments are supported, such as %example.', array('%example' => 'http://www.example.com/?query=value#fragment')); + // if (rabbit_hole_access_php($module)) { + // $placeholders = array( + // '!surround' => '<?php and ?>', + // '!abort' => 'FALSE', + // '!variable' => '$entity', + // ); + // $description[] = t("You are able to evaluate PHP to determine the redirect. Surround your code by !surround. The returned string will replace the PHP part. However, you are able to return !abort if the user shouldn't get redirected. The !variable variable is available for use.", $placeholders); + // } + $description[] = t('You may enter tokens in this field.'); + + $form['rabbit_hole']['redirect']['rh_redirect'] = array( + '#type' => /*rabbit_hole_access_php($module) ? 'textarea' :*/ 'textfield', + '#title' => t('Redirect path'), + '#default_value' => $redirect_path, + '#description' => '

' . implode('

', $description) . '

', + '#attributes' => array('class' => array('rabbit-hole-redirect-setting')), + '#rows' => substr_count($redirect_path, "\r\n") + 2, + ); + // Display a list of tokens if the Token module is enabled. + // if (module_exists('token')) { + // $entity_info = entity_get_info($entity_type); + // $form['rabbit_hole']['redirect']['token_info'] = array( + // '#theme' => 'token_tree', + // '#token_types' => array($entity_info['token type']), + // '#dialog' => TRUE, + // ); + // } + + // Add the redirect response setting. + $form['rabbit_hole']['redirect']['rh_redirect_response'] = array( '#type' => 'select', '#title' => $this->t('Response code'), - '#description' => $this->t('he response code that should be sent to the users browser. Follow this link for more information on response codes.'), - '#options' => [], - '#default_value' => $this->code, - ]; + '#options' => array( + 301 => $this->t('301 (Moved Permanently)'), + 302 => $this->t('302 (Found)'), + 303 => $this->t('303 (See other)'), + 304 => $this->t('304 (Not modified)'), + 305 => $this->t('305 (Use proxy)'), + 307 => $this->t('307 (Temporary redirect)'), + ), + '#default_value' => $redirect_code, + '#description' => $this->t('The response code that should be sent to the users browser. Follow @link for more information on response codes.', + array('@link' => Link::fromTextAndUrl(t('this link'), Url::fromUri('http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_goto/7'))->toString())), + '#attributes' => array('class' => array('rabbit-hole-redirect-response-setting')), + ); + + // If the redirect path contains PHP, and the user doesn't have permission to + // use PHP for evaluation, we'll disable access to the path setting, and print + // some helpful information about what's going on. + // if (rabbit_hole_contains_php($redirect_default_value) && !rabbit_hole_access_php($module)) { + // $form['rabbit_hole']['redirect']['#description'] = t("You're not able to edit the redirect path since it contain's PHP, and you're not allowed to evaluate PHP for this redirect."); + // $form['rabbit_hole']['redirect'][$redirect_setting_name]['#access'] = FALSE; + // if (isset($form['rabbit_hole']['redirect']['token_info'])) { + // $form['rabbit_hole']['redirect']['token_info']['#access'] = FALSE; + // } + // } } + /** + * {@inheritdoc} + */ + public function alterExtraFields(array &$fields) { + $fields['rh_redirect'] = BaseFieldDefinition::create('string') + ->setLabel($this->t('Rabbit Hole redirect path or code')) + ->setDescription($this->t('The path to where the user should get redirected to.')); + $fields['rh_redirect_response'] = BaseFieldDefinition::create('integer') + ->setLabel($this->t('Rabbit Hole redirect response code')) + ->setDescription($this->t('Specifies the HTTP response code that should be used when perform a redirect.')); + } } diff --git a/src/Plugin/RabbitHoleBehaviorPluginBase.php b/src/Plugin/RabbitHoleBehaviorPluginBase.php index ce5b771..243ef19 100644 --- a/src/Plugin/RabbitHoleBehaviorPluginBase.php +++ b/src/Plugin/RabbitHoleBehaviorPluginBase.php @@ -6,6 +6,8 @@ namespace Drupal\rabbit_hole\Plugin; +use Drupal\Core\Entity\Entity; +use Drupal\Core\Config\ImmutableConfig; use Drupal\Component\Plugin\PluginBase; /** @@ -16,15 +18,36 @@ abstract class RabbitHoleBehaviorPluginBase extends PluginBase implements Rabbit /** * {@inheritdoc} */ - public function performAction() { + public function performAction(Entity $entity) { // Perform no action. } /** * {@inheritdoc} */ - public function settingsForm(&$form, &$form_state, $form_id) { + public function settingsForm(&$form, &$form_state, $form_id, Entity $entity = NULL, + $entity_is_bundle = FALSE, ImmutableConfig $bundle_settings = NULL) { // Present no settings form. } + /** + * {@inheritdoc} + */ + public function settingsFormHandleSubmit(&$form, &$form_state) { + // No extra action to handle submission by default + } + + /** + * {@inheritdoc} + */ + public function alterExtraFields(array &$fields) { + // Don't change the fields by default + } + + /** + * {@inheritdoc} + */ + public function usesResponse() { + return RabbitHoleBehaviorPluginInterface::USES_RESPONSE_NEVER; + } } diff --git a/src/Plugin/RabbitHoleBehaviorPluginInterface.php b/src/Plugin/RabbitHoleBehaviorPluginInterface.php index 20dc525..13f51a2 100644 --- a/src/Plugin/RabbitHoleBehaviorPluginInterface.php +++ b/src/Plugin/RabbitHoleBehaviorPluginInterface.php @@ -7,6 +7,8 @@ namespace Drupal\rabbit_hole\Plugin; +use Drupal\Core\Entity\Entity; +use Drupal\Core\Config\ImmutableConfig; use Drupal\Component\Plugin\PluginInspectionInterface; /** @@ -14,10 +16,19 @@ use Drupal\Component\Plugin\PluginInspectionInterface; */ interface RabbitHoleBehaviorPluginInterface extends PluginInspectionInterface { + const USES_RESPONSE_NEVER = 0; + const USES_RESPONSE_SOMETIMES = 1; + const USES_RESPONSE_ALWAYS = 2; + /** * Perform the rabbit hole action. + * @param $entity + * The entity the action is being performed on + * @param $current_response + * The response as it currently exists before being replaced by the response + * returned by the rabbit hole plugin */ - public function performAction(); + public function performAction(Entity $entity); /** * Return a settings form for the rabbit hole action. @@ -28,7 +39,37 @@ interface RabbitHoleBehaviorPluginInterface extends PluginInspectionInterface { * The form state array to modify. * @param string $form_id * The form ID. + * @param Entity $entity + * The entity used by the form + * @param boolean $entity_is_bundle + * Whether the entity is a bundle + * @param BehaviorSettings + * The behavior settings for the bundle of the entity (or the entity itself, + * if it is a bundle) + */ + public function settingsForm(&$form, &$form_state, $form_id, Entity $entity = NULL, + $entity_is_bundle = FALSE, ImmutableConfig $bundle_settings = NULL); + + /** + * Handle submission of the settings form for this plugin + */ + public function settingsFormHandleSubmit(&$form, &$form_state); + + /** + * Add to or adjust the fields added by rabbit hole. + * @param $fields The array of fields to be altered */ - public function settingsForm(&$form, &$form_state, $form_id); + public function alterExtraFields(array &$fields); + /** + * Get whether this plugin uses a response to perform its action + * + * Override this to return one of USES_RESPONSE_NEVER, USES_RESPONSE_SOMETIMES, + * or USES_RESPONSE_ALWAYS to indicate whether performAction should be invoked + * only when a null response is given, regardless of whether there is + * a response (it'll figure out what to do with or without on its own), or + * only when a non-null response is given. Defaults to returning + * USES_RESPONSE_NEVER. + */ + public function usesResponse(); } diff --git a/src/Tests/RabbitHoleBehaviorPluginTest.php b/src/Tests/RabbitHoleBehaviorPluginTest.php index 13c3ab3..d6cd28d 100644 --- a/src/Tests/RabbitHoleBehaviorPluginTest.php +++ b/src/Tests/RabbitHoleBehaviorPluginTest.php @@ -7,6 +7,7 @@ namespace Drupal\rabbit_hole\Tests; use Drupal\system\Tests\Plugin\PluginTestBase; +use Drupal\node\Entity\Node; /** * Test the functionality of the RabbitHoleBehavior plugin. @@ -138,5 +139,4 @@ class RabbitHoleBehaviorPluginTest extends PluginTestBase { // TODO: Check that $plugin->performAction() does what it's supposed to, // whatever that is. } - }