diff --git a/configuration_example/README.txt b/configuration_example/README.txt new file mode 100644 index 0000000..faf81ca --- /dev/null +++ b/configuration_example/README.txt @@ -0,0 +1,15 @@ +Configuration Example +Part of Examples for developers +(http://drupal.org/project/examples) + +This module demonstrates how to set up configuration for a module. + +Note the difference between configuration and content. Content types, Image Styles, Taxonomy vocabularies are configuration. Nodes, Images, and Taxonomy terms are content. This module shows you how to manage configuration, not content, in your module. + +In Drupal 7, both configuration and content are stored in the database, so you will notice a resemblance between this module, meant to demonstrate configuration settings, and the dbtng_example module, meant to demonstrate how modules can store content in the database. (In Drupal 8, the techniques are completely different for storing configuration and content.) + +Two configuration types are demonstrated here: + +(1) simple configuration: site-wide configuration variables. Our example uses "Configuration example variable one" and "Configuration example variable two". + +(2) complex configuration: variable number of configuration sets. Our example lets you define as many "kittens" as you want, and for each kitten, define a color and size. \ No newline at end of file diff --git a/configuration_example/configuration_example.info b/configuration_example/configuration_example.info new file mode 100644 index 0000000..7756b21 --- /dev/null +++ b/configuration_example/configuration_example.info @@ -0,0 +1,5 @@ +name = Configuration example +description = An example module showing how to set up configuration settings for your module, both simple (variable-based), and complex (using a database schema). +package = Example modules +core = 7.x +files[] = configuration_example.test diff --git a/configuration_example/configuration_example.install b/configuration_example/configuration_example.install new file mode 100644 index 0000000..2897781 --- /dev/null +++ b/configuration_example/configuration_example.install @@ -0,0 +1,131 @@ + 'white_kitten', + 'name' => 'White kitten', + 'color' => 'white', + ); + db_insert('configuration_example') + ->fields($fields) + ->execute(); + + // Another sample configuration set. + $fields = array( + 'kid' => 'yellow_kitten', + 'name' => 'Yellow kitten', + 'color' => 'yellow', + ); + db_insert('configuration_example') + ->fields($fields) + ->execute(); +} + +/** + * Implements hook_uninstall(). + * + * As in hook_install, there is no need to uninstall schema, Drupal will do it + * for us. We will simply delete our variables. + * + * @see hook_uninstall() + * @ingroup configuration_example + */ +function configuration_example_uninstall() { + // Delete any variables that might have been set by the module. + variable_del('configuration_example_one'); + variable_del('configuration_example_two'); +} + + +/** + * Implements hook_schema(). + * + * Defines the database tables used by this module. + * Remember that the easiest way to create the code for hook_schema is with + * the @link http://drupal.org/project/schema schema module @endlink + * + * In our case, we are defining one table to hold our complex site + * configuration. Every complex configuration set will be stored as a + * single row in the resulting database. The first two sample rows will be + * created upon installation of this module (see + * configuration_example_install(), above). + * + * Our simple variable-based configuration uses Drupal's variable + * system so we don't need to define anything here for that. + * + * @see hook_schema() + * @ingroup configuration_example + */ +function configuration_example_schema() { + + $schema['configuration_example'] = array( + 'description' => 'Stores example kitten entries for demonstration purposes.', + 'fields' => array( + // kid stands for "kitten id". If you are storing something else, + // like beach balls, you might call your primary field bbid. + 'kid' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'Primary Key: Unique kitten ID (machine name).', + ), + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'Kitten\'s name.', + ), + 'color' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Kitten\'s color.', + ), + ), + 'primary key' => array('kid'), + 'indexes' => array( + 'color' => array('color'), + ), + ); + + return $schema; +} diff --git a/configuration_example/configuration_example.module b/configuration_example/configuration_example.module new file mode 100644 index 0000000..92b1df0 --- /dev/null +++ b/configuration_example/configuration_example.module @@ -0,0 +1,449 @@ + 'Configuration example - Simple', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('configuration_example_form_simple'), + 'access callback' => TRUE, + ); + $items['examples/configuration-complex'] = array( + 'title' => 'Configuration example - Complex', + 'page callback' => 'configuration_example_complex_list', + 'access callback' => TRUE, + ); + $items['examples/configuration-complex/list'] = array( + 'title' => 'Complex configuration list', + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items['examples/configuration-complex/add'] = array( + 'title' => 'Add a complex configuration item', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('configuration_example_form_add'), + 'access callback' => TRUE, + 'type' => MENU_LOCAL_TASK, + ); + $items['examples/configuration-complex/%/update'] = array( + 'title' => 'Update (edit) a complex configuration item', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('configuration_example_form_update'), + 'access callback' => TRUE, + ); + $items['examples/configuration-complex/%/delete'] = array( + 'title' => 'Delete entry', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('configuration_example_form_delete'), + 'access callback' => TRUE, + ); + + return $items; +} + +/** + * Save an new complex configuration entry in the database. + * + * In our example a complex configuration entry is a type of kitten. + * + * Called by configuration_example_form_add_submit() when the user + * visits the complex configuration page and saves a new configuration + * set. For example, a user might create a new configuration set + * "green kitten" with the machine name "green_kitten", and + * the values name: "green kitten" color:"green". + * + * Third-party modules can also call this function if required. + * + * @param $entry + * An array containing all the fields of the database record: + * - kid (kitten id): the kitten's machine name (should not + * already exist in the database). + * - name: the human readable name of the kitten. + * - color: the color of the kitten. + * + * @see db_insert() + */ +function configuration_example_entry_insert($entry) { + $return_value = NULL; + try { + $return_value = db_insert('configuration_example') + ->fields($entry) + ->execute(); + } + catch (Exception $e) { + drupal_set_message(t('db_insert failed. Message = %message, query= %query', + array('%message' => $e->getMessage(), '%query' => $e->query_string)), 'error'); + } + return $return_value; +} + +/** + * Update a complex configuration entry in the database. + * + * In our example a complex configuration entry is a type of kitten. + * + * Called by configuration_example_form_update_submit() when a user + * has visited the config administration screen to update a + * configuration entry. Third-party modules can also call this function + * if required. + * + * For this to work, the kid, or kitten id (the machine name of the + * configuration entry) should already exist in the database. + * + * @param $entry + * An array containing all the fields of the item to be updated, with + * the kid (kitten id) field referring to an already-existing entry + * in the database. + * - kid (kitten id): the kitten's existing machine name. + * - name: the human readable name of the kitten. + * - color: the new color of the kitten. + * + * @see db_update() + */ +function configuration_example_entry_update($entry) { + try { + // db_update()...->execute() returns the number of rows updated. + $count = db_update('configuration_example') + ->fields($entry) + ->condition('kid', $entry['kid']) + ->execute(); + } + catch (Exception $e) { + drupal_set_message(t('db_update failed. Message = %message, query= %query', + array('%message' => $e->getMessage(), '%query' => $e->query_string)), 'error'); + } + return $count; +} + +/** + * Delete a complex configuration entry based on its machine name. + * + * In our example a complex configuration entry is a type of kitten. Our + * machine name is the "kid" (kitten id) in the database. + * + * Called by configuration_example_form_delete_submit() when the user + * has confirmed that the configuration entry for the machine name kid + * should be deleted. Third-party modules can also call this function + * if required. + * + * @param $kid + * kid (kitten id) which already exists in the database and should be + * deleted. + */ +function configuration_example_entry_delete($kid) { + db_delete('configuration_example') + ->condition('kid', $kid) + ->execute(); +} + +/** + * Display a form to delete an entry from the database. + * + * Displays a form when visiting examples/configuration-complex/%/delete. + */ +function configuration_example_form_delete($form, &$form_state) { + $kid = arg(2); + if (!configuration_example_kid_exists($kid)) { + drupal_set_message(t('The kitten @kid does not exist and cannot be deleted.', array('@kid' => check_plain($kid)))); + drupal_goto('examples/configuration-complex/list'); + } + + $form['kid'] = array( + '#type' => 'hidden', + '#value' => $kid, + ); + + return confirm_form( + $form, + t('Are you sure you want to delete kitten @kid?', + array('@kid' => $kid)), + 'examples/configuration-complex' + ); +} + +/** + * Submit handler for deleting a complex config entry. + */ +function configuration_example_form_delete_submit($form, &$form_state) { + // Instead of performing the delete operation here, we will call + // an API function, passing only the value of kid (kitten ID) + // as an argument. This allows third-party modules to call the delete + // function without simulating an actual form. + configuration_example_entry_delete($form['kid']['#value']); + + // set a success message and go back to the list, otherwise we'll stay on + // the same delete path of an item which no longer exists. + drupal_set_message(t('The kitten @kid has successfully been deleted.', array('@kid' => check_plain($form['kid']['#value'])))); + drupal_goto('examples/configuration-complex/list'); +} + +/** + * Read one or all complex entries from the database. + * + * @param $kid + * a machine id of a kitten to load. Ignored if the kid is + * invalid or does not exist. If such is the case, all + * entries are returned. + * + * @return + * An object containing the loaded entries if found. + */ +function configuration_example_entry_load($kid = NULL) { + // Read all fields from the configuration_example table. + $select = db_select('configuration_example', 'example'); + $select->fields('example'); + if (configuration_example_kid_exists($kid)) { + $select->condition('kid', $kid); + } + + // Return the result in object format. + return $select->execute()->fetchAll(); +} + +/** + * Render complex configuration list for display on-screen. + */ +function configuration_example_complex_list() { + $output = ''; + + // Get all entries in the configuration_example table. + if ($entries = configuration_example_entry_load()) { + $rows = array(); + foreach ($entries as $entry) { + // Sanitize the data before handing it off to the theme layer. + $row = array_map('check_plain', (array) $entry); + $row[] = l(t('edit'), 'examples/configuration-complex/' . $row['kid'] . '/update'); + $row[] = l(t('delete'), 'examples/configuration-complex/' . $row['kid'] . '/delete'); + $rows[] = $row; + } + // Make a table for them. + $header = array(t('Machine name'), t('Name'), t('color'), t('edit'), t('delete')); + $output .= theme('table', array('header' => $header, 'rows' => $rows)); + } + else { + drupal_set_message(t('No kittens are defined.')); + } + return $output; +} + +/** + * Prepare a simple form to add an entry, with all the fields we need. + */ +function configuration_example_form_add($form, &$form_state) { + $form = array(); + + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Kitten name'), + '#required' => TRUE, + '#description' => t('User-friendly name.'), + '#size' => 40, + '#maxlength' => 127, + '#default_value' => '', + ); + $form['kid'] = array( + '#type' => 'machine_name', + '#required' => TRUE, + '#title' => t("Machine Name"), + '#required' => TRUE, + '#description' => t('machine-friendly name.'), + '#size' => 15, + '#maxlength' => 15, + '#default_value' => '', + '#machine_name' => array( + 'exists' => 'configuration_example_kid_exists', + 'source' => array('name'), + 'label' => t('Machine nmae'), + 'replace_pattern' => '[^a-z0-9-]+', + 'replace' => '-', + ), + ); + $form['color'] = array( + '#type' => 'textfield', + '#required' => TRUE, + '#title' => t('Color'), + '#size' => 15, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Add'), + ); + + return $form; +} + +/** + * Submit handler for 'add entry' form. + */ +function configuration_example_form_add_submit($form, &$form_state) { + // Save the submitted entry. + $entry = array( + 'kid' => $form_state['values']['kid'], + 'name' => $form_state['values']['name'], + 'color' => $form_state['values']['color'], + ); + $return = configuration_example_entry_insert($entry); + if ($return) { + drupal_set_message(t("Created entry @entry", array('@entry' => print_r($entry, TRUE)))); + } + + $form_state['redirect'] = 'examples/configuration-complex'; + + return $return; +} + +/** + * Sample UI to update a record. + */ +function configuration_example_form_update($form, &$form_state) { + $kid = arg(2); + if (!configuration_example_kid_exists($kid)) { + drupal_set_message(t('The kitten @kid does not exist and cannot be updated.', array('@kid' => check_plain($kid)))); + drupal_goto('examples/configuration-complex/list'); + } + + $entries = configuration_example_entry_load($kid); + + $form['kid'] = array( + '#type' => 'hidden', + '#value' => $kid, + ); + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Name'), + '#size' => 15, + '#default_value' => $entries[0]->name, + ); + $form['color'] = array( + '#type' => 'textfield', + '#title' => t('Color'), + '#size' => 15, + '#default_value' => $entries[0]->color, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Update'), + ); + return $form; +} + +/** + * Submit handler for 'update entry' form. + */ +function configuration_example_form_update_submit($form, &$form_state) { + // Save the submitted entry. + $entry = array( + 'kid' => $form_state['values']['kid'], + 'name' => $form_state['values']['name'], + 'color' => $form_state['values']['color'], + ); + $count = configuration_example_entry_update($entry); + drupal_set_message(t("Updated entry @entry (@count row updated)", array('@count' => $count, '@entry' => print_r($entry, TRUE)))); + + $form_state['redirect'] = 'examples/configuration-complex'; +} + +/** + * Returns whether a kitten machine name already exists. + * + * @param @kid + * A kitten ID. + */ +function configuration_example_kid_exists($kid) { + return db_query_range('SELECT 1 FROM {configuration_example} WHERE kid = :kid', 0, 1, array(':kid' => $kid))->fetchField(); +} + +/** + * Sample UI to update a simple configuration item. + */ +function configuration_example_form_simple($form, &$form_state) { + $form['configuration_example_one'] = array( + '#type' => 'textfield', + '#title' => t('Configuration example variable one'), + '#size' => 15, + '#default_value' => variable_get('configuration_example_one', 'Default value one'), + ); + $form['configuration_example_two'] = array( + '#type' => 'textfield', + '#title' => t('Configuration example variable two'), + '#size' => 15, + '#default_value' => variable_get('configuration_example_two', 'Default value two'), + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Update'), + ); + + return $form; +} + +/** + * Submit handler for 'configuration_example_form_update' form. + */ +function configuration_example_form_simple_submit($form, &$form_state) { + // you can do what you want with the variables. Let's store them + // to the database's variable table. + variable_set('configuration_example_one', $form_state['input']['configuration_example_one']); + variable_set('configuration_example_two', $form_state['input']['configuration_example_two']); + drupal_set_message(t('Successfully updated the simple configuration variables')); +} diff --git a/configuration_example/configuration_example.test b/configuration_example/configuration_example.test new file mode 100644 index 0000000..1485745 --- /dev/null +++ b/configuration_example/configuration_example.test @@ -0,0 +1,62 @@ + 'Configuration example UI tests', + 'description' => 'Various unit tests on the configuration example module.' , + 'group' => 'Examples', + ); + } + + function setUp() { + parent::setUp('configuration_example'); + } + + function testUI() { + // simple config + $this->drupalGet('examples/configuration-simple'); + $this->assertRaw('Default value one', '"Default value one", provided by default, is present.'); + $this->drupalPost(NULL, array('configuration_example_one' => 'Updated value one'), t('Update')); + $this->assertNoRaw('Default value one', '"Default value one" has been updated and is no longer present.'); + $this->assertRaw('Updated value one', '"Updated value one", entered by the user, is present.'); + + // complex config + $this->drupalGet('examples/configuration-complex'); + $this->assertText('White kitten', 'The kitten "White kitten", installed by default, is present after installation'); + $new_name = $this->randomName(8); + $new_kid = strtolower($this->randomName(8)); + $new_color = $this->randomName(8); + $edit = array( + 'name' => $new_name, + 'kid' => $new_kid, + 'color' => $new_color, + ); + $this->drupalPost('examples/configuration-complex/add', $edit, t('Add')); + $this->assertText($new_name, 'The name of the new kitten is present'); + $this->assertText($new_kid, 'The machine name of the new kitten is present'); + $this->assertText($new_color, 'The color of the new kitten is present'); + + $new_color = $this->randomName(8); + $this->drupalPost('examples/configuration-complex/white_kitten/update', array('color' => $new_color), t('Update')); + $this->assertText($new_color, 'Color has been updated'); + + $this->drupalPost('examples/configuration-complex/white_kitten/delete', array(), t('Confirm')); + $this->assertNoText('White kitten', 'White kitten has been deleted, is no longer present'); + $this->assertNoText(t('The kitten @kid does not exist and cannot be deleted.', array('@kid' => check_plain('white_kitten'))), 'Deleting a config set does not cause an erroneous "does not exist" message to appear.'); + $this->assertText('Yellow kitten', 'Deleting "White kitten" has not deleted "Yellow kitten"'); + + $this->drupalGet('examples/configuration-complex/machine_name_does_not_exist/delete'); + $this->assertText(t('The kitten @kid does not exist and cannot be deleted.', array('@kid' => 'machine_name_does_not_exist')), 'trying to delete a non-existant machien name fails gracefully'); + $this->drupalGet('examples/configuration-complex/machine_name_does_not_exist/update'); + $this->assertText(t('The kitten @kid does not exist and cannot be updated.', array('@kid' => 'machine_name_does_not_exist')), 'trying to update a non-existant machien name fails gracefully'); + } +}