diff --git a/configuration_example/README.txt b/configuration_example/README.txt new file mode 100644 index 0000000..1c24ec5 --- /dev/null +++ b/configuration_example/README.txt @@ -0,0 +1,13 @@ +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. + +Two configuration types are demonstrated: + +(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..1221551 --- /dev/null +++ b/configuration_example/configuration_example.info @@ -0,0 +1,5 @@ +name = Configuration example +description = An example module showing how to set up configurations for your module. +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..a0b97f3 --- /dev/null +++ b/configuration_example/configuration_example.install @@ -0,0 +1,115 @@ + 'big_white_kitten', + 'name' => 'Big white kitten', + 'color' => 'white', + 'size' => 'big', + ); + db_insert('configuration_example') + ->fields($fields) + ->execute(); + + // Add a few entries. + $fields = array( + 'kid' => 'small_yellow_kitten', + 'name' => 'Small yellow kitten', + 'color' => 'small', + 'size' => '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 variable. + * + * @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 + * + * @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.', + ), + 'size' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Kitten\'s solor.', + ), + ), + 'primary key' => array('kid'), + 'indexes' => array( + 'color' => array('color'), + 'size' => array('size'), + ), + ); + + return $schema; +} diff --git a/configuration_example/configuration_example.module b/configuration_example/configuration_example.module new file mode 100644 index 0000000..71992a7 --- /dev/null +++ b/configuration_example/configuration_example.module @@ -0,0 +1,397 @@ +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 an entry in the database. + * + * @param $entry + * An array containing all the fields of the item to be updated. + * + * @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 an entry from the database. + * + * @param $entry + * An array containing at least the kitten identifier 'kid' element of the + * entry to delete. + * + * @see db_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' + ); +} + +function configuration_example_form_delete_submit($form, &$form_state) { + db_delete('configuration_example') + ->condition('kid', $form['kid']['#value']) + ->execute(); +} + +/** + * Read all entries from the database. + * + * @param $kid + * a machine id of a kitten to load. Ignored if the kid is + * invalid or does not exist + * + * @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(); +} + +/** + * Implements hook_help(). + * + * Show some help on each form provided by this module. + */ +function configuration_example_help($path) { + $output = ''; + switch ($path) { + case 'examples/configuration-simple': + $output = t('Demonstrates how to manage simple site-wide configuration options.'); + break; + case 'examples/configuration-complex': + $output = t('Demonstrates how to manage complex groups of configuration data for your site.'); + break; + case 'examples/configuration-complex/%/delete': + $output = t('Demonstrates how to delete a configuration group.'); + break; + case 'examples/configuration-complex/%/edit': + $output = t('Demonstrates how to edit a configuration group.'); + break; + case 'examples/configuration-complex/add': + $output = t('Demonstrates how to add a configuration group.'); + break; + } + return $output; +} + +/** + * Implements hook_menu(). + * + * Set up calls to drupal_get_form() for all our example cases. + */ +function configuration_example_menu() { + $items = array(); + + $items['examples/configuration-simple'] = array( + 'title' => '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; +} + +/** + * Render a list of entries in the database. + */ +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('size'), 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 interesting fields. + */ +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['size'] = array( + '#type' => 'textfield', + '#required' => TRUE, + '#title' => t('Size'), + '#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'], + 'size' => $form_state['values']['size'], + '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['size'] = array( + '#type' => 'textfield', + '#title' => t('Size'), + '#size' => 15, + '#default_value' => $entries[0]->size, + ); + + $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'], + 'size' => $form_state['values']['size'], + ); + $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. + * + * @TODO this works, but there also is a way (which I would not recommend) + * to get the FAPI to automatically store variables in the variables table. + * I can't get it to work so I'm leaving it at this. + */ +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..a4da603 --- /dev/null +++ b/configuration_example/configuration_example.test @@ -0,0 +1,63 @@ + '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('Big white kitten', 'The kitten "Big 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); + $new_size = $this->randomName(8); + $edit = array( + 'name' => $new_name, + 'kid' => $new_kid, + 'color' => $new_color, + 'size' => $new_size, + ); + $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'); + $this->assertText($new_size, 'The size of the new kitten is present'); + + $new_color = $this->randomString(8); + $this->drupalPost('examples/configuration-complex/big_white_kitten/update', array('color' => $new_color), t('Update')); + $this->assertText($new_color, 'Color has been updated'); + + $this->drupalPost('examples/configuration-complex/big_white_kitten/delete', array(), t('Confirm')); + $this->assertNoText('Big white kitten', 'Big white kitten has been deleted, is no longer present'); + + $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'); + } +}