diff --git a/config/install/rabbit_hole.behavior_settings.default.yml b/config/install/rabbit_hole.behavior_settings.default.yml deleted file mode 100644 index 3602e5a..0000000 --- a/config/install/rabbit_hole.behavior_settings.default.yml +++ /dev/null @@ -1,2 +0,0 @@ -id: default -action: "display_page" diff --git a/config/install/rabbit_hole.behavior_settings.default_bundle.yml b/config/install/rabbit_hole.behavior_settings.default_bundle.yml new file mode 100644 index 0000000..8bc5ad6 --- /dev/null +++ b/config/install/rabbit_hole.behavior_settings.default_bundle.yml @@ -0,0 +1,6 @@ +id: default_bundle +action: "display_page" +# Allow +allow_override: 1 +# N/A +redirect_code: 0 diff --git a/config/schema/behavior_settings.schema.yml b/config/schema/behavior_settings.schema.yml index 39cba35..340ca53 100644 --- a/config/schema/behavior_settings.schema.yml +++ b/config/schema/behavior_settings.schema.yml @@ -9,3 +9,13 @@ rabbit_hole.behavior_settings.*: type: string action: type: string + label: 'Action' + allow_override: + type: integer + label: 'Allow override' + redirect_path: + type: string + label: 'Redirect path' + redirect_code: + type: integer + label: 'Redirect code' diff --git a/modules/rh_node/rh-node.js b/modules/rh_node/rh-node.js new file mode 100644 index 0000000..36dceea --- /dev/null +++ b/modules/rh_node/rh-node.js @@ -0,0 +1,18 @@ +(function($) { + +Drupal.behaviors.rhNode = { + attach: function (context, settings) { + + // Set the summary for the settings form. + $('fieldset.rabbit-hole-settings-form').drupalSetSummary(function() { + var $rabbitHoleAction = $('.rabbit-hole-action-setting input:checked'); + + // Get the label of the selected action. + var summary = $('label[for=' + $rabbitHoleAction.attr('id') + ']').text(); + return Drupal.checkPlain(summary); + }); + + } +} + +})(jQuery); diff --git a/modules/rh_node/rh_node.info.yml b/modules/rh_node/rh_node.info.yml new file mode 100644 index 0000000..99b0fe2 --- /dev/null +++ b/modules/rh_node/rh_node.info.yml @@ -0,0 +1,7 @@ +name: Rabbit Hole nodes +type: module +description: Adds Rabbit Hole functionality for nodes. +core: 8.x +dependencies: + - rabbit_hole + - node diff --git a/modules/rh_node/rh_node.libraries.yml b/modules/rh_node/rh_node.libraries.yml new file mode 100644 index 0000000..d913efe --- /dev/null +++ b/modules/rh_node/rh_node.libraries.yml @@ -0,0 +1,6 @@ +node-form: + version: 1.x + js: + rh-node.js: {} + dependencies: + - core/jquery diff --git a/modules/rh_node/rh_node.module b/modules/rh_node/rh_node.module new file mode 100644 index 0000000..e8843da --- /dev/null +++ b/modules/rh_node/rh_node.module @@ -0,0 +1,47 @@ +addRabbitHoleOptionsToForm( + $form, 'rh_node', $form_state->getFormObject()->getEntity()); + $form['#attached']['library'][] = 'rh_node/node-form'; +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * This will add Rabbit Hole options to the node form. The user will be able to + * override the default Rabbit Hole options. + */ +function rh_node_form_node_form_alter(&$form, $form_state) { + // Add the Rabbit Hole form, and add an extra javascript file that's needed + // for the fieldset summary. + \Drupal::service('rabbit_hole.form_mangler')->addRabbitHoleOptionsToForm( + $form, 'rh_node', $form_state->getFormObject()->getEntity()); + $form['#attached']['library'][] = 'rh_node/node-form'; + + $form_state->setSubmitHandlers( + array_merge($form_state->getSubmitHandlers(), + array('_rabbit_hole_general_form_submit')) + ); + // dpm('[#submit]'); + // dpm($form['#submit']); + // dpm('[actions][submit][#submit]'); + // dpm($form['actions']['submit']['#submit']); + // dpm('[actions][publish][#submit]'); + // dpm($form['actions']['publish']['#submit']); + + // Add a custom submit function. This is used to disable the redirect to + // node/123 if Rabbit Hole is enabled and the user doesn't have the bypass + // rh_node permission. + // if (!user_access('bypass rh_node')) { + // $form['actions']['submit']['#submit'][] = 'rh_node_node_form_submit'; + // } +} diff --git a/modules/rh_node/src/Tests/BehaviorSettingsFormAlterationsTest.php b/modules/rh_node/src/Tests/BehaviorSettingsFormAlterationsTest.php new file mode 100644 index 0000000..496e76d --- /dev/null +++ b/modules/rh_node/src/Tests/BehaviorSettingsFormAlterationsTest.php @@ -0,0 +1,283 @@ +user = $this->drupalCreateUser(array( + 'bypass node access', 'administer content types' + )); + + $this->behaviorSettingsManager = $this->container + ->get('rabbit_hole.behavior_settings_manager'); + } + + /** + * Test that the content type form of a newly created content type contains + * the expected fields + */ + public function testDefaultContentTypeForm() { + $this->createTestContentType(); + $this->loadContentTypeFormForTestType(); + + $this->assertFieldByName('rh_override', BehaviorSettings::OVERRIDE_ALLOW); + $this->assertFieldByName('rh_action', 'access_denied'); + $this->assertFieldByName('rh_action', 'display_page'); + $this->assertFieldByName('rh_action', 'page_not_found'); + $this->assertFieldByName('rh_action', 'page_redirect'); + $default_option_id = 'edit-rh-action-' + . str_replace('_', '-', self::DEFAULT_BUNDLE_ACTION); + $this->assertFieldChecked($default_option_id); + } + + /** + * Test that saving changes to a content type form creates an appropriate + * behavior settings config key + */ + public function testContentTypeFormFirstSave() { + $test_content_type_id = $this->createTestContentType(); + $this->loadContentTypeFormForTestType(); + + $override = BehaviorSettings::OVERRIDE_DISALLOW; + $action = 'access_denied'; + + $this->drupalPostForm(NULL, array( + 'rh_override' => $override, + 'rh_action' => $action + ), t('Save content type')); + + $saved_config = $this->behaviorSettingsManager->loadBehaviorSettingsAsConfig( + 'node_type', $test_content_type_id, TRUE); + $this->assertEqual($saved_config->get('action'), $action); + $this->assertEqual($saved_config->get('allow_override'), $override); + } + + /** + * Test that a content type form of a content type with a configured behavior + * properly loads configuration + */ + public function testContentTypeFormExistingBehavior() { + $action = 'page_not_found'; + $override = BehaviorSettings::OVERRIDE_DISALLOW; + + $test_content_type_id = $this->createTestContentType(); + $this->behaviorSettingsManager->saveBehaviorSettings(array( + 'action' => $action, + 'allow_override' => $override, + 'redirect_code' => BehaviorSettings::REDIRECT_NOT_APPLICABLE, + ), 'node_type', $test_content_type_id + ); + + $this->loadContentTypeFormForTestType(); + + $this->assertFieldByName('rh_override', $override); + $default_option_id = 'edit-rh-action-' + . str_replace('_', '-', $action); + $this->assertFieldChecked($default_option_id); + } + + /** + * Test that saving changes to a content type form which already has configured + * rabbit hole behavior settings changes the existing key + */ + public function testContentTypeFormSave() { + $test_content_type_id = $this->createTestContentType(); + + $this->behaviorSettingsManager->saveBehaviorSettings(array( + 'action' => 'access_denied', + 'allow_override' => BehaviorSettings::OVERRIDE_DISALLOW, + 'redirect_code' => BehaviorSettings::REDIRECT_NOT_APPLICABLE, + ), 'node_type', $test_content_type_id + ); + + $this->loadContentTypeFormForTestType(); + + $action = 'page_not_found'; + $override = BehaviorSettings::OVERRIDE_ALLOW; + + $this->drupalPostForm(NULL, array( + 'rh_override' => $override, + 'rh_action' => $action + ), t('Save content type')); + + $saved_config = $this->behaviorSettingsManager->loadBehaviorSettingsAsConfig( + 'node_type', $test_content_type_id, TRUE); + + $this->assertEqual($saved_config->get('action'), $action); + $this->assertEqual($saved_config->get('allow_override'), $override); + } + + /** + * Test that an existing node that previously didn't have settings will have + * settings saved when the node form is saved + */ + public function testExistingNodeNoConfigSave() { + $this->createTestContentType(); + $node_id = $this->createTestNode(); + + $action = 'access_denied'; + + $this->loadNodeFormForTestNode($node_id); + $this->drupalPostForm(NULL, array( + 'rh_action' => $action + ), t('Save')); + + $node = Node::Load($node_id); + $this->assertEqual($node->rh_action->value, $action); + } + + /** + * Test that an existing node entity is edited on saving the node form + */ + public function testExistingNodeSave() { + $this->createTestContentType(); + $node_id = $this->createTestNode('display_page'); + + $action = 'access_denied'; + + $this->loadNodeFormForTestNode($node_id); + $this->drupalPostForm(NULL, array( + 'rh_action' => $action + ), t('Save')); + + $node = Node::Load($node_id); + $this->assertEqual($node->rh_action->value, $action); + } + + /** + * Test that when a node form is loaded it defaults to using the bundle + * configuration + */ + public function testDefaultNodeSettingsLoad() { + $this->createTestContentType(); + $this->loadNewNodeFormForTestContentType(); + + $this->assertNoFieldByName('rh_override'); + $this->assertFieldByName('rh_action', 'access_denied'); + $this->assertFieldByName('rh_action', 'display_page'); + $this->assertFieldByName('rh_action', 'page_not_found'); + $this->assertFieldByName('rh_action', 'page_redirect'); + $default_option_id = 'edit-rh-action-' + . str_replace('_', '-', self::DEFAULT_ACTION); + $this->assertFieldChecked($default_option_id); + } + + /** + * Test that a node form correctly loads previously saved rabbit hole behavior + * settings + */ + public function testExistingNodeSettingsLoad() { + $this->createTestContentType(); + + $action = 'access_denied'; + $node_id = $this->createTestNode($action); + $this->loadNodeFormForTestNode($node_id); + $default_option_id = 'edit-rh-action-' + . str_replace('_', '-', $action); + $this->assertFieldChecked($default_option_id); + } + + /** + * Create a content type for testing + * @return string The content type ID + */ + private function createTestContentType() { + $node_type = NodeType::create( + array( + 'type' => self::TEST_CONTENT_TYPE_ID, + 'name' => self::TEST_CONTENT_TYPE_ID + ) + ); + $node_type->save(); + return $node_type->id(); + } + + /** + * Create a node for testing + * @return integer The node ID + */ + private function createTestNode($action = '') { + $node = Node::create( + array( + 'nid' => NULL, + 'type' => self::TEST_CONTENT_TYPE_ID, + 'title' => 'Test Behavior Settings Node', + ) + ); + if (isset($action)) { + $node->set('rh_action', $action); + } + $node->save(); + return $node->id(); + } + + /** + * Load the test content type form + */ + private function loadContentTypeFormForTestType() { + $this->drupalLogin($this->user); + $this->drupalGet(self::CONTENT_TYPE_PATH_PREFIX + . self::TEST_CONTENT_TYPE_ID); + $this->assertResponse(200); + } + + /** + * Load the add new node form for the test content type + */ + private function loadNewNodeFormForTestContentType() { + $this->drupalLogin($this->user); + $this->drupalGet(self::CONTENT_ADD_PREFIX + . self::TEST_CONTENT_TYPE_ID); + $this->assertResponse(200); + } + + /** + * Load the node form for the test node with the given ID + * @param $test_node_id The ID of the test node + */ + private function loadNodeFormForTestNode($test_node_id) { + $this->drupalLogin($this->user); + $this->drupalGet('node/' . $test_node_id . '/edit'); + $this->assertResponse(200); + } +} diff --git a/rabbit_hole.module b/rabbit_hole.module index 404dd67..c922125 100644 --- a/rabbit_hole.module +++ b/rabbit_hole.module @@ -6,6 +6,7 @@ */ use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Field\BaseFieldDefinition; /** * Implements hook_help(). @@ -24,6 +25,29 @@ function rabbit_hole_help($route_name, RouteMatchInterface $route_match) { } /** + * Implements hook_entity_base_field_info(). + * @todo Change this to load entity types from plugins + * @todo Possibly rh_redirect and rh_redirect_response could be handled by plugin? + */ +function rabbit_hole_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) { + // 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; + } +} + +/** * Implements hook_theme(). */ function rabbit_hole_theme() { @@ -31,3 +55,13 @@ function rabbit_hole_theme() { return $theme; } + +/** + * Handle general aspects of rabbit hole form submission + * @param type $form + * @param type &$form_state + */ +function _rabbit_hole_general_form_submit($form, $form_state) { + \Drupal::service('rabbit_hole.form_mangler') + ->handleFormSubmit($form, $form_state); +} diff --git a/rabbit_hole.services.yml b/rabbit_hole.services.yml index 57c8882..1860ff0 100644 --- a/rabbit_hole.services.yml +++ b/rabbit_hole.services.yml @@ -2,3 +2,14 @@ services: plugin.manager.rabbit_hole_behavior_plugin: class: Drupal\rabbit_hole\Plugin\RabbitHoleBehaviorPluginManager parent: default_plugin_manager + rabbit_hole.behavior_settings_manager: + class: Drupal\rabbit_hole\BehaviorSettingsManager + arguments: ["@config.factory"] + rabbit_hole.form_mangler: + class: Drupal\rabbit_hole\FormManglerService + arguments: + - '@entity_type.manager' + - '@entity_type.bundle.info' + - '@plugin.manager.rabbit_hole_behavior_plugin' + - '@rabbit_hole.behavior_settings_manager' + - "@string_translation" diff --git a/src/BehaviorSettingsInterface.php b/src/BehaviorSettingsInterface.php index af94d0f..61628d0 100644 --- a/src/BehaviorSettingsInterface.php +++ b/src/BehaviorSettingsInterface.php @@ -14,9 +14,51 @@ use Drupal\Core\Config\Entity\ConfigEntityInterface; */ interface BehaviorSettingsInterface extends ConfigEntityInterface { // Add get/set methods for your configuration properties here. + + /** + * Set the configured action + * @param string $action The action to save + */ + public function setAction($action); + /** * Get the configured action * @return string The action id */ public function getAction(); + + /** + * Set whether overrides are allowed if this is for a bundle + * @param integer $allow_override 0 (N/A), 1 (Allow) or 2 (Disallow) + */ + public function setAllowOverride($allow_override); + + /** + * Get whether overrides are allowed if this is for a bundle + * @return integer + */ + public function getAllowOverride(); + + /** + * Set the redirect code if action is redirect + * @param integer $redirect_code The redirect code (0 for N/A) + */ + public function setRedirectCode($redirect_code); + + /** + * Get the redirect code if action is redirect + */ + public function getRedirectCode(); + + /** + * Set the redirect path if action is redirect + * @param string $redirect_path The redirect path + */ + public function setRedirectPath($redirect_path); + + /** + * Get the redirect path if action is redirect + * @return string The redirect path + */ + public function getRedirectPath(); } diff --git a/src/BehaviorSettingsManager.php b/src/BehaviorSettingsManager.php new file mode 100644 index 0000000..617e0b0 --- /dev/null +++ b/src/BehaviorSettingsManager.php @@ -0,0 +1,105 @@ +config_factory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public function saveBehaviorSettings($settings, $entity_type_id, $entity_id, + $is_bundle = FALSE) { + + $id = $this->generateBehaviorSettingsFullId($entity_type_id, $entity_id, + $is_bundle); + + $entity = BehaviorSettings::load($id); + if ($entity === NULL) { + $entity_array = array('id' => $id); + $entity_array += $settings; + $entity = BehaviorSettings::create($entity_array); + } + else { + foreach ($settings as $key => $setting) { + $entity->set($key, $setting); + } + } + $entity->save(); + } + + /** + * {@inheritdoc} + */ + public function loadBehaviorSettingsAsConfig($entity_type_id, + $entity_id = NULL, $is_bundle = FALSE) { + + $actual = $this->config_factory->get( + 'rabbit_hole.behavior_settings.' + . $this->generateBehaviorSettingsFullId($entity_type_id, $entity_id, + $is_bundle + ) + ); + if (!$actual->isNew()) { + return $actual; + } + else { + return $this->config_factory + ->get('rabbit_hole.behavior_settings.default_bundle'); + } + } + + /** + * {@inheritdoc} + */ + public function loadBehaviorSettingsAsEditableConfig($entity_type_id, + $entity_id, $is_bundle = FALSE) { + + $actual = $this->config_factory->getEditable( + 'rabbit_hole.behavior_settings.' + . $this->generateBehaviorSettingsFullId($entity_type_id, $entity_id, + $is_bundle + ) + ); + return !$actual->isNew() ? $actual : NULL; + } + + + /** + * Generate a full ID based on entity type label, bundle label and entity id + * @param string $entity_type_id The entity type (e.g. node) as a string + * @param string $entity_id The entity ID as a string + * @return string The full id appropriate for a BehaviorSettings config entity + */ + private function generateBehaviorSettingsFullId($entity_type_id, + $entity_id = '', $is_bundle = FALSE) { + return $entity_type_id . (isset($entity_id) ? '_' . $entity_id : ''); + } +} diff --git a/src/BehaviorSettingsManagerInterface.php b/src/BehaviorSettingsManagerInterface.php new file mode 100644 index 0000000..ba91d9c --- /dev/null +++ b/src/BehaviorSettingsManagerInterface.php @@ -0,0 +1,48 @@ +action = $action; + } + + /** + * {@inheritdoc} + */ public function getAction() { return $this->action; } + + /** + * {@inheritdoc} + */ + public function setAllowOverride($allow_override) { + if (!is_bool($allow_override)) { + throw new InvalidBehaviorSettingException('allow_override'); + } + $this->allow_override = $allow_override; + } + + /** + * {@inheritdoc} + */ + public function getAllowOverride() { + return $this->allow_override; + } + + /** + * {@inheritdoc} + * @todo Possibly this should instead rely on the redirect plugin? + */ + public function setRedirectCode($redirect_code) { + if (!in_array($redirect_code, array( + self::REDIRECT_NOT_APPLICABLE, + self::REDIRECT_MOVED_PERMANENTLY, + self::REDIRECT_FOUND, + self::REDIRECT_SEE_OTHER, + self::REDIRECT_NOT_MODIFIED, + self::REDIRECT_USE_PROXY, + self::REDIRECT_TEMPORARY_REDIRECT + ) + )) { + throw new InvalidBehaviorSettingException('redirect_code'); + } + + if ($this->action !== 'redirect' + && $redirect_code !== self::REDIRECT_NOT_APPLICABLE) { + throw new InvalidBehaviorSettingException('redirect_code'); + } + $this->redirect_code = $redirect_code; + } + + /** + * {@inheritdoc} + */ + public function getRedirectCode() { + return $this->redirect_code; + } + + /** + * {@inheritdoc} + */ + public function setRedirectPath($redirect_path) { + if ($this->action !== 'redirect' && $redirect_path != "") { + throw new InvalidBehaviorSettingException('redirect_path'); + } + $this->redirect_path = $redirect_path; + } + + /** + * {@inheritdoc} + */ + public function getRedirectPath() { + return $this->redirect_path; + } } diff --git a/src/Exception/InvalidBehaviorSettingException.php b/src/Exception/InvalidBehaviorSettingException.php new file mode 100644 index 0000000..a79aa42 --- /dev/null +++ b/src/Exception/InvalidBehaviorSettingException.php @@ -0,0 +1,16 @@ +setting = $setting; + } + + public function getSetting() { + return $this->setting(); + } +} diff --git a/src/FormManglerService.php b/src/FormManglerService.php new file mode 100644 index 0000000..83d2e23 --- /dev/null +++ b/src/FormManglerService.php @@ -0,0 +1,371 @@ +entityTypeManager = $etm; + $this->allBundleInfo = $etbi->getAllBundleInfo(); + $this->rhBehaviorPluginManager = $behaviorPluginManager; + $this->rhBehaviorSettingsManager = $behaviorSettingsManager; + $this->stringTranslation = $translation; + } + + // Add common methods and abstract methods for your plugin type here. + + /** + * Form structure for the Rabbit Hole configuration. + * + * This should be used by other modules that wish to implement the Rabbit Hole + * configurations in any form. + * + * @param array $attach + * The form that the Rabbit Hole form should be attached to. + * @param string $entity_type + * The entity type that we're adding the form for, e.g. 'node'. + * @param string $bundle + * The bundle that we're adding the form to, e.g. the content type for nodes. + * This might be an empty string if we're creating a new bundle. + * @param string $module + * The name of the module that invokes this function. + * @param object $entity + * The entity that we're adding the form to, e.g. a node. This should be + * defined even in the case of bundles since it is used to determine bundle + * and entity type. + */ + public function addRabbitHoleOptionsToForm(array &$attach, $module, Entity $entity) { + $bundle = $entity->bundle(); + $entity_type = $entity->getEntityType(); + $entity_is_bundle = $this->isEntityBundle($entity); + + $bundle_settings = NULL; + + $action = NULL; + $redirect_path = NULL; + $redirect_code = NULL; + + if ($entity_is_bundle) { + $bundle_settings = $this->rhBehaviorSettingsManager + ->loadBehaviorSettingsAsConfig($entity_type->id(), + $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 + ->loadBehaviorSettingsAsConfig($entity_type->getBundleEntityType(), + $entity->bundle()); + + $action = isset($entity->rh_action->value) + ? $entity->rh_action->value + : 'bundle_default'; + $redirect_path = isset($entity->rh_redirect->value) + ? $entity->rh_redirect->value + : ''; + $redirect_code = isset($entity->rh_redirect_code->value) + ? $entity->rh_redirect_code->value + : NULL; + } + + // if (!user_access('administer ' . $module)) { + // The user doesn't have access, exit. + // return; + // } + + // if (isset($entity) && !rabbit_hole_get_override_bundle($entity_type, $bundle)) { + // // The form is about to be attached to an entity, but the bundle isn't + // // allowed to be overridden. Exit. + // return; + // } + + // if (isset($entity) + // && !$this->getOverrideBundle($entity_type->getLabel(), $bundle)) { + // return; + // } + + // Get information about the entity. + // $entity_info = entity_get_info($entity_type); + // $entity_label = strtolower(isset($entity_info['plural label']) ? $entity_info['plural label'] : $entity_info['label']); + /** + * @todo Should be possible to get this as plural? Look into this + */ + $entity_label = $entity_type->getLabel(); + + $bundleInfo = isset($this->allBundleInfo[$entity_type->id()]) + ? $this->allBundleInfo[$entity_type->id()] : NULL; + + // Get the label for the bundle. This won't be set when the user is creating a + // new bundle. In that case, fallback to "this bundle". + // $bundle_label = isset($entity_info['bundles'][$bundle]['label']) ? $entity_info['bundles'][$bundle]['label'] : 'this bundle'; + $bundle_label = NULL !== $bundleInfo && NULL !== $bundleInfo[$bundle]['label'] + ? $bundleInfo[$bundle]['label'] : $this->t('this bundle'); + + // Wrap everything in a fieldset. + $form['rabbit_hole'] = array( + '#type' => 'details', + '#title' => $this->t('Rabbit Hole settings'), + '#collapsed' => FALSE, + '#collapsible' => TRUE, + /** + * @todo Should probably handle group in a plugin - not sure if, e.g., + * files will work in the same way and even if they do later + * entities might not + */ + '#group' => $entity_is_bundle ? 'additional_settings' : 'advanced', + '#attributes' => array('class' => array('rabbit-hole-settings-form')), + ); + // $form['path']['#group'] = 'rabbit_hole'; + + // Add the invoking module to the internal values. + $form['rabbit_hole']['rh_module'] = array( + '#type' => 'value', + '#value' => $module, + ); + $form['rabbit_hole']['rh_is_bundle'] = array( + '#type' => 'hidden', + '#value' => $entity_is_bundle, + ); + + // Add override setting if we're editing a bundle. + if ($entity_is_bundle) { + $allow_override = $bundle_settings->get('allow_override'); + $form['rabbit_hole']['rh_override'] = array( + '#type' => 'checkbox', + '#title' => t('Allow these settings to be overridden for individual entities'), + '#default_value' => $allow_override, + '#description' => t('If this is checked, users with the %permission permission will be able to override these settings for individual entities.', array('%permission' => t('Administer Rabbit Hole settings for @entity_type', array('@entity_type' => $entity_label)))), + ); + } + + // Add action setting. + $action_options = $this->loadBehaviorOptions(); + + if (!$entity_is_bundle) { + // Add an option if we are editing an entity. This will allow us to use the + // configuration for the bundle. + $action_bundle = $bundle_settings->get('action'); + $action_options = array( + self::RABBIT_HOLE_USE_DEFAULT => t('Global @bundle behavior (@setting)', array('@bundle' => strtolower($bundle_label), '@setting' => $action_options[$action_bundle])), + ) + $action_options; + } + + $form['rabbit_hole']['rh_action'] = array( + '#type' => 'radios', + '#title' => $this->t('Behavior'), + '#options' => $action_options, + '#default_value' => $action, + '#description' => $this->t('What should happen when someone tries to visit an entity page for @bundle?', array('@bundle' => strtolower(isset($entity_info['plural label']) ? $entity_info['plural label'] : $bundle_label))), + '#attributes' => array('class' => array('rabbit-hole-action-setting')), + ); + + /** + * @todo Add redirect settings + */ + // Wrap the redirect settings in a fieldset. + // $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="' . $action_setting_name . '"]' => array('value' => '3'), + // ), + // ), + // ); + + // 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[] = $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.', 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[] = $this->t('You may enter tokens in this field.'); + + // Add the redirect path setting. + // $redirect_setting_name = isset($entity) ? 'rh_redirect' : 'rh_' . $entity_type->id() . '_redirect'; + // $form['rabbit_hole']['redirect']['redirect_setting_name'] = array( + // '#type' => 'value', + // '#value' => $redirect_setting_name, + // ); + // $form['rabbit_hole']['redirect'][$redirect_setting_name] = array( + // // '#type' => rabbit_hole_access_php($module) ? 'textarea' : 'textfield', + // '#type' => 'textfield', + // '#title' => t('Redirect path'), + // // '#default_value' => $redirect_default_value, + // '#description' => '

' . implode('

', $description) . '

', + // '#attributes' => array('class' => array('rabbit-hole-redirect-setting')), + // // '#rows' => substr_count($redirect_default_value, "\r\n") + 2, + // ); + + /** + * @todo Add token support + */ + // 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, + // ); + // } + + /** + * @todo Add specific options for redirect - possibly via the plugin? + */ + // Add the redirect respons setting. + // $redirect_response_setting_name = isset($entity) ? 'rh_redirect_response' : 'rh_' . $entity_type . '_redirect_response'; + // $form['rabbit_hole']['redirect'][$redirect_response_setting_name] = array( + // '#type' => 'select', + // '#title' => t('Response 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' => isset($entity) ? rabbit_hole_get_redirect_response_entity($entity_type, $entity) : (!empty($bundle) ? rabbit_hole_get_redirect_response_bundle($entity_type, $bundle) : RABBIT_HOLE_PAGE_REDIRECT_RESPONSE_DEFAULT), + // '#description' => $this->t('The response code that should be sent to the users browser. Follow !link for more information on response codes.', array('!link' => l(t('this link'), 'http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_goto/7'))), + // '#attributes' => array('class' => array('rabbit-hole-redirect-response-setting')), + // ); + + /** + * @todo Handle possible PHP code in redirect + */ + // 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; + // } + // } + // Attach the Rabbit Hole form to the main form, and add a custom validation + // callback. + $attach += $form; + + /** + * @todo Optionally provide a form validation handler (can we do this via plugin?) + */ + // $attach['#validate'][] = 'rabbit_hole_form_validate'; + + // If the implementing module provides a submit function for the bundle form, + // we'll add it as a submit function for the attached form. We'll also make + // sure that this won't be added for entity forms. + + /** + * @todo This should probably be moved out into plugins based on entity type + */ + $attach['actions']['submit']['#submit'][] = '_rabbit_hole_general_form_submit'; + $attach['actions']['publish']['#submit'][] = '_rabbit_hole_general_form_submit'; + + /** + * @todo Optionally provide additional form submission handler (can we do this via plugin?) + */ + // $submit_function = $module . '_bundle_form_submit'; + // if (function_exists($submit_function) && !isset($entity)) { + // $attach['#submit'][] = $submit_function; + // } + } + + /** + * Handle general aspects of rabbit hole form submission (not specific to node + * etc.) + * @param array $form + * @param type &$form_state + */ + public function handleFormSubmit($form, $form_state) { + if ($form_state->getValue('rh_is_bundle')) { + $entity = $form_state->getFormObject()->getEntity(); + $allow_override = $form_state->getValue('rh_override') + ? BehaviorSettings::OVERRIDE_ALLOW + : BehaviorSettings::OVERRIDE_DISALLOW; + + $this->rhBehaviorSettingsManager->saveBehaviorSettings( + array( + 'action' => $form_state->getValue('rh_action'), + 'allow_override' => $allow_override, + 'redirect_path' => $form_state->getValue('rh_redirect_path') + ?: '', + 'redirect_code' => $form_state->getValue('rh_redirect_code') + ?: BehaviorSettings::REDIRECT_NOT_APPLICABLE, + ), + $entity->getEntityType()->id(), + $entity->id(), + $form_state->getValue('rh_is_bundle') + ); + } + } + + /** + * Load an array of rabbit hole behavior options from plugins in the format + * option id => label + * @return array An array of behavior options + */ + protected function loadBehaviorOptions() { + $action_options = array(); + foreach ($this->rhBehaviorPluginManager->getDefinitions() as $id => $def) { + $action_options[$id] = $def['label']; + } + return $action_options; + } + + protected function isEntityBundle($entity) { + return is_subclass_of($entity, + 'Drupal\Core\Config\Entity\ConfigEntityBundleBase'); + } +} diff --git a/src/Tests/RabbitHoleBehaviorSettingsEntityMethodsTest.php b/src/Tests/RabbitHoleBehaviorSettingsEntityMethodsTest.php new file mode 100644 index 0000000..9a43f19 --- /dev/null +++ b/src/Tests/RabbitHoleBehaviorSettingsEntityMethodsTest.php @@ -0,0 +1,234 @@ +configFactory = $this->container->get('config.factory'); + } + + /** + * Test creating a BehaviorSettings entity and loading it as config + */ + public function testCreateEntity() { + $action = 'page_not_found'; + $redirect_code = BehaviorSettings::REDIRECT_NOT_APPLICABLE; + $redirect_path = '/'; + $allow_override = BehaviorSettings::OVERRIDE_ALLOW; + + $entity = BehaviorSettings::create( + array( + 'id' => 'test_behavior_settings', + 'action' => $action, + 'allow_override' => $allow_override, + 'redirect_code' => $redirect_code, + 'redirect_path' => $redirect_path, + ) + ); + $entity->save(); + $config_entity = $this->configFactory + ->get('rabbit_hole.behavior_settings.test_behavior_settings'); + + $this->assertEqual($action, $config_entity->get('action')); + $this->assertEqual($redirect_code, $config_entity->get('redirect_code')); + $this->assertEqual($redirect_path, $config_entity->get('redirect_path')); + $this->assertEqual($allow_override, $config_entity->get('allow_override')); + } + + /** + * Test that creating an entity with an invalid redirect code fails + * @note Currently config entity constructors don't use setters - see + * https://www.drupal.org/node/2399999 + */ + // public function testCreateEntityInvalidRedirectCode() { + // $action = 'redirect'; + // $redirect_code = 16709278; + // $redirect_path = '/'; + // $exception_was_thrown = FALSE; + // try { + // $entity = BehaviorSettings::create( + // array( + // 'id' => 'test_behavior_settings', + // 'action' => $action, + // 'redirect_code' => $redirect_code, + // 'redirect_path' => $redirect_path, + // ) + // ); + // } catch (InvalidBehaviorSettingException $ex) { + // $exception_was_thrown = TRUE; + // } + // $this->assertTrue($exception_was_thrown); + // } + + /** + * Test that creating an entity with redirect settings when the action + * type is not redirect fails + * @note Currently config entity constructors don't use setters - see + * https://www.drupal.org/node/2399999 + */ + // public function testCreateEntityRedirectSettingsForWrongAction() { + // $action = 'display_page'; + // $redirect_code_none = BehaviorSettings::REDIRECT_NOT_APPLICABLE; + // $redirect_path_none = ''; + // $redirect_code = BehaviorSettings::REDIRECT_FOUND; + // $redirect_path = '/'; + + // $exception_was_thrown = FALSE; + // try { + // $entity = BehaviorSettings::create( + // array( + // 'id' => 'test_behavior_settings', + // 'action' => $action, + // 'redirect_code' => $redirect_code, + // 'redirect_path' => $redirect_path_none, + // ) + // ); + // } catch (InvalidBehaviorSettingException $ex) { + // $exception_was_thrown = TRUE; + // } + // $this->assertTrue($exception_was_thrown); + + // $exception_was_thrown = FALSE; + // try { + // $entity = BehaviorSettings::create( + // array( + // 'id' => 'test_behavior_settings_2', + // 'action' => $action, + // 'redirect_code' => $redirect_code_none, + // 'redirect_path' => $redirect_path, + // ) + // ); + // } catch (InvalidBehaviorSettingException $ex) { + // $exception_was_thrown = TRUE; + // } + // $this->assertTrue($exception_was_thrown); + // } + + /** + * Test that setAction() works as expected + */ + public function testSetAction() { + $entity = $this->createGenericTestEntity(); + $action = 'page_not_found'; + $entity->setAction($action); + $this->assertTrue($action, $entity->getAction()); + } + + /** + * Test that setAllowOverride works as expected (including throwing an + * exception if an invalid value is passed) + */ + public function testSetAllowOverride() { + $entity = $this->createGenericTestEntity(); + + $this->testBehaviorSettingExceptionThrown($entity, + 'setAllowOverride', array('some non-bool value'), __METHOD__); + $entity->setAllowOverride(TRUE); + $this->assertEqual(TRUE, $entity->getAllowOverride()); + $entity->setAllowOverride(FALSE); + $this->assertEqual(FALSE, $entity->getAllowOverride()); + } + + /** + * Test that setRedirectCode works as expected (including throwing an + * exception for invalid codes and settings codes when action type is wrong) + */ + public function testSetRedirectCode() { + $entity = $this->createGenericTestEntity(); + + $entity->setAction('display_page'); + $this->testBehaviorSettingExceptionThrown($entity, 'setRedirectCode', + array(BehaviorSettings::REDIRECT_FOUND), __METHOD__); + + $entity->setAction('redirect'); + + $this->testBehaviorSettingExceptionThrown($entity, 'setRedirectCode', + array(209458253), __METHOD__); + + $entity->setRedirectCode(BehaviorSettings::REDIRECT_FOUND); + $this->assertEqual(BehaviorSettings::REDIRECT_FOUND, + $entity->getRedirectCode()); + + $entity->setRedirectCode(BehaviorSettings::REDIRECT_MOVED_PERMANENTLY); + $this->assertEqual(BehaviorSettings::REDIRECT_MOVED_PERMANENTLY, + $entity->getRedirectCode()); + } + + /** + * Test that setRedirectPath works as expected (including throwing an + * exception for invalid codes and settings codes when action type is wrong) + */ + public function testSetRedirectPath() { + $entity = $this->createGenericTestEntity(); + + $entity->setAction('display_page'); + $this->testBehaviorSettingExceptionThrown($entity, 'setRedirectPath', + array('/'), __METHOD__); + + $entity->setAction('redirect'); + $path = '/somepage'; + $entity->setRedirectPath($path); + $this->assertEqual($path, $entity->getRedirectPath()); + } + + /** + * Create a generic test BehaviorSettings entity + */ + private function createGenericTestEntity() { + return BehaviorSettings::create( + array( + 'id' => 'test_behavior_settings', + 'action' => 'access_denied', + 'redirect_code' => BehaviorSettings::REDIRECT_NOT_APPLICABLE, + 'redirect_path' => NULL + ) + ); + } + + /** + * Test that a BehaviorSettingException gets thrown when $entity executes + * $method with $args. This uses call_user_func internally. + * @param BehaviorSettings $entity The BehaviorSettings entity + * @param string $method The method to call + * @param array $args The arguments to pass to the method + * @param string $parent The name of the method which calls this method + */ + private function testBehaviorSettingExceptionThrown(BehaviorSettings $entity, $method, array $args, $parent) { + $exception_was_thrown = FALSE; + try { + call_user_func(array($entity, $method), $args); + } catch (InvalidBehaviorSettingException $ex) { + $exception_was_thrown = TRUE; + } + $this->assert($exception_was_thrown, 'Exception thrown executing ' + . $method . ', called from ' . $parent); + } +} diff --git a/src/Tests/RabbitHoleBehaviorSettingsTest.php b/src/Tests/RabbitHoleBehaviorSettingsTest.php index 7efa46e..3ccd155 100644 --- a/src/Tests/RabbitHoleBehaviorSettingsTest.php +++ b/src/Tests/RabbitHoleBehaviorSettingsTest.php @@ -6,32 +6,39 @@ namespace Drupal\rabbit_hole\Tests; -use Drupal\system\Tests\Plugin\PluginTestBase; +use Drupal\simpletest\WebTestBase; +use Drupal\node\Entity\NodeType; +use Drupal\node\Entity\Node; use Drupal\rabbit_hole\Entity\BehaviorSettings; -// use Drupal\node\Entity\NodeType; -// use Drupal\node\Entity\Node; /** * Test the functionality of the RabbitHoleBehaviorSettings configuration entity. * * @group rabbit_hole */ -class RabbitHoleBehaviorSettingsTest extends PluginTestBase { +class RabbitHoleBehaviorSettingsTest extends WebTestBase { + const DEFAULT_TEST_ENTITY = 'node'; + const DEFAULT_ACTION = 'bundle_default'; + const DEFAULT_OVERRIDE = BehaviorSettings::OVERRIDE_ALLOW; + const DEFAULT_REDIRECT_CODE = BehaviorSettings::REDIRECT_NOT_APPLICABLE; + const DEFAULT_BUNDLE_ACTION = 'display_page'; + const DEFAULT_BUNDLE_OVERRIDE = BehaviorSettings::OVERRIDE_ALLOW; + const DEFAULT_BUNDLE_REDIRECT_CODE = BehaviorSettings::REDIRECT_NOT_APPLICABLE; /** * Modules to enable. * * @var array */ - public static $modules = array('rabbit_hole'); + public static $modules = array('rabbit_hole', self::DEFAULT_TEST_ENTITY); - private $config; + private $behaviorSettingsManager; private $configFactory; - // private $testNodeType; + private $testNodeType; - // private $testNode; + private $testNode; /** * {@inheritdoc} @@ -39,8 +46,10 @@ class RabbitHoleBehaviorSettingsTest extends PluginTestBase { protected function setUp() { parent::setUp(); $this->configFactory = $this->container->get('config.factory'); - // $this->testNodeType = $this->generateTestNodeType(); - // $this->testNode = $this->generateTestNode(); + $this->behaviorSettingsManager = $this->container + ->get('rabbit_hole.behavior_settings_manager'); + $this->testNodeType = $this->generateTestNodeType(); + $this->testNode = $this->generateTestNode(); } /** @@ -48,113 +57,140 @@ class RabbitHoleBehaviorSettingsTest extends PluginTestBase { * and contains the correct values */ public function testSettings() { - $this->saveAndTestExpectedValues('test_behavior_settings', 'display_page', - TRUE); + $this->saveAndTestExpectedValues(self::DEFAULT_ACTION, + __METHOD__, '', 'test_behavior_settings'); } /** - * @todo This test is failing - seems defaults are not being picked up. Why? - * Some inconsistent information about path, seems most likely reason - have - * tried config/install (as stated here: https://www.drupal.org/node/2120571) - * and src/install/config (from Drupalize.me video which previously seemed - * to work) + * Test that the default bundle settings exist and have the expected values */ - public function testSettingsDefault() { - $action = \Drupal::config('rabbit_hole.behavior_settings.default') - ->get('action'); - $this->assertEqual($action, 'display_page', 'Unexpected default action'); + public function testBundleSettingsDefault() { + $settings = \Drupal::config('rabbit_hole.behavior_settings.default_bundle'); + $this->assertEqual($settings->get('action'), + self::DEFAULT_BUNDLE_ACTION, + 'Unexpected default action'); + $this->assertEqual($settings->get('allow_override'), + self::DEFAULT_BUNDLE_OVERRIDE, 'Unexpected default override'); + $this->assertEqual($settings->get('redirect_code'), + self::DEFAULT_BUNDLE_REDIRECT_CODE, 'Unexpected default redirect'); } /** * Test that a saved BehaviourSettings entity can be given an ID based on * a generated bundle (a NodeType in this case) and be found based on that ID */ - // public function testBundleSettings() { - // $this->createTestNodeType(); - // $this->saveAndTestExpectedValues($this->testNodeType->id(), - // 'page_not_found', TRUE, 'bundle_'); - // $this->deleteTestNodeType(); - // } + public function testBundleSettings() { + $this->createTestNodeType(); + $this->saveAndTestExpectedValues('page_not_found', __METHOD__, + self::DEFAULT_TEST_ENTITY, $this->testNodeType->id()); + $this->deleteTestNodeType(); + } /** - * Test that a saved BehaviourSettings entity can be given an ID based on - * a generated entity (a Node in this case) and be found based on that ID + * Test loading behavior settings for a nonexistent bundle returns defaults + * @return type */ - // public function testEntitySettings() { - // $this->createTestNodeType(); - // $this->createTestNode(); - // $this->saveAndTestExpectedValues($this->testNode->id(), 'access_denied', - // TRUE, 'entity_'); - // $this->deleteTestNode(); - // $this->deleteTestNodeType(); - // } + public function testLoadBundleSettingsWithDefault() { + // We search for a bundle that doesn't exist (named from a UUID) expecting + // to receive the default value + $action = $this->behaviorSettingsManager->loadBehaviorSettingsAsConfig( + self::DEFAULT_TEST_ENTITY, + 'f4515736-cfa0-4e38-b3ed-1306f56bd2a1')->get('action'); + $this->assertEqual(self::DEFAULT_BUNDLE_ACTION, $action, + 'Unexpected default action'); + } - // public function testLoadBundleSettingsWithDefault() {} + /** + * Test loading editable for nonexistent behavior settings returns NULL + */ + public function testLoadNullEditable() { + $editable = $this->behaviorSettingsManager + ->loadBehaviorSettingsAsEditableConfig(self::DEFAULT_TEST_ENTITY, + '6b92ed36-f17f-4799-97d0-ae1801ed37ff'); + $this->assertEqual($editable, NULL); + } - // public function testLoadEntitySettingsWithDefault() {} + /** + * Helper function to test saving and confirming config + */ + private function saveAndTestExpectedValues($expected_action, + $calling_method, $entity_type_label = '', $entity_id = NULL) { - private function saveAndTestExpectedValues($id, $expected_action, $id_prefix = "") { // Delete key if it already exists - $this->configFactory->getEditable('rabbit_hole.behavior_settings.' . $id) - ->delete(); + $editable = $this->behaviorSettingsManager->loadBehaviorSettingsAsEditableConfig( + $entity_type_label, $entity_id); + if (isset($editable)) { + $editable->delete(); + } - $entity = BehaviorSettings::create( - array( - 'id' => $id_prefix . $id, + $this->behaviorSettingsManager->saveBehaviorSettings(array( 'action' => $expected_action, - ) - ); - $entity->save(); - - $action = \Drupal::config( - 'rabbit_hole.behavior_settings.' . $id_prefix . $id)->get('action'); - - $this->assertEqual($action, $expected_action, 'Unexpected action'); + 'allow_override' => 0, + 'redirect_code' => 0, + 'redirect_path' => '', + ), $entity_type_label, $entity_id); + $action = $this->behaviorSettingsManager->loadBehaviorSettingsAsConfig( + $entity_type_label, $entity_id)->get('action'); + $this->assertEqual($action, $expected_action, 'Unexpected action ' + . ' (called from ' . $calling_method . ')'); // Clean up the entity afterwards - $entity->delete(); + $this->behaviorSettingsManager->loadBehaviorSettingsAsEditableConfig( + $entity_type_label, $entity_id)->delete(); } - // TODO - // private function generateTestNodeType() { - // return \entity_create('node_type', - // array( - // 'type' => 'test_behavior_settings_node_type', - // 'name' => 'Test Behavior Settings Node Type' - // ) - // ); - // } + /** + * Helper function to generate the test node type + */ + private function generateTestNodeType() { + return \entity_create('node_type', + array( + 'type' => 'test_behavior_settings_node_type', + 'name' => 'Test Behavior Settings Node Type' + ) + ); + } - // TODO - // private function generateTestNode() { - // return Node::create( - // array( - // 'nid' => NULL, - // 'type' => $this->testNodeType->id(), - // 'title' => 'Test Behavior Settings Node', - // ) - // ); - // } + /** + * Helper function to generate the test node + */ + private function generateTestNode() { + return Node::create( + array( + 'nid' => NULL, + 'type' => $this->testNodeType->id(), + 'title' => 'Test Behavior Settings Node', + ) + ); + } - // TODO - // private function createTestNodeType() { - // $this->testNodeType->save(); - // } + /** + * Helper function to create the test node type in the database + */ + private function createTestNodeType() { + $this->testNodeType->save(); + } - // TODO - // private function deleteTestNodeType() { - // $this->testNodeType->delete(); - // } + /** + * Helper function to delete the test node type from the database + */ + private function deleteTestNodeType() { + $this->testNodeType->delete(); + } - // TODO - // private function createTestNode() { - // $this->testNode->save(); - // } + /** + * Helper function to create the test node in the database + */ + private function createTestNode() { + $this->testNode->save(); + } - // TODO - // private function deleteTestNode() { - // $this->testNode->delete(); - // } + /** + * Helper function to delete the test node from the database + */ + private function deleteTestNode() { + $this->testNode->delete(); + } // TODO // public function testSaveBundleSettingsViaForm() {}