07. Create a module configuration (settings) page
Main topic described: Module settings
Drupal hook used: hook_menu
Now that we have a working module, we'd like to make it better. If we have a site that has been around for a while, content from a week ago might not be as interesting as content from a year ago. Similarly, if we have a busy site, we might not want to display all the links to content created last week. So, let's create a configuration page for the administrator to adjust this information.
Create the configuration function
We'd like to configure how many links display in the block, so we'll create a form for the administrator to set the number of links. This is done in our function onthisdate_admin. Note that _admin is not a hook and we could have used whatever we wanted there.
<?php
function onthisdate_admin() {
$form['onthisdate_maxdisp'] = array(
'#type' => 'textfield',
'#title' => t('Maximum number of links'),
'#default_value' => variable_get('onthisdate_maxdisp', 3),
'#size' => 2,
'#maxlength' => 2,
'#description' => t("The maximum number of links to display in the block."),
'#required' => TRUE,
);
return system_settings_form($form);
}
?>This function uses several powerful Drupal form handling features. We don't need to worry about creating an HTML text field or the form, as Drupal will do so for us. We use variable_get to retrieve the value of the system configuration variable "onthisdate_maxdisp", and define the default value to be 3. We use an array ( named $form here ) to create the form which contains a textfield of size 2 ( #size ), accepting a maximum length of 2 characters ( #maxlength ). We also use the translate function of t(). The system_settings_form() function adds default buttons to a form and set its prefix.
Refer to Drupal Forms API Reference and
Drupal Forms API Quickstart Guide for more detailed information on what more you can do with Drupal Form API.
When you save a settings variable for any module, the variable (in our case, 'onthisdate_maxdisp') and the value is stored in the variables table. Programmatically, you can retrieve the values with the variable_get('variable_name', default_value) function.
Of course, we'll need to use the configuration value in our SQL SELECT, so we'll need to adjust our query statement in the onthisdate_block function. One way to do this is with a LIMIT value in our query:
<?php
$limitnum = variable_get('onthisdate_maxdisp', 3);
$query = "SELECT nid, title, created FROM " .
"{node} WHERE created >= '" . $start_time .
"' AND created <= '". $end_time . "' LIMIT " . $limitnum;
?>However, this method may or may not translate across databases (really). Better to use one of Drupal's select methods. In this case, let's leave the original query the same, and call db_query_range:
<?php
$limitnum = variable_get("onthisdate_maxdisp", 3);
$query = "SELECT nid, title, created FROM " .
"{node} WHERE created >= %d " .
"AND created <= %d";
$queryResult = db_query_range($query, $start_time, $end_time, 0, $limitnum);
?>Add the page to hook_menu
Once you have created the function with your settings, the page is added with a callback to the function which you specify in hook_menu. In hook_menu we will return an array which describes to Drupal which URL path to use, the title to display, the function to use and the permissions required.
We would like only administrators to be able to access this page, so we'll place the permissions check for the module here in hook_menu so that Drupal can itself check the appropriate permission. To minimize the number of permissions an administrator has to deal with, we're going to use the global administration permission for administrating our module instead of creating a new custom permission.
<?php
function onthisdate_menu() {
$items = array();
$items['admin/settings/onthisdate'] = array(
'title' => 'On this date module settings',
'description' => 'Description of your On this date settings control',
'page callback' => 'drupal_get_form',
'page arguments' => array('onthisdate_admin'),
'access arguments' => array('access administration pages'),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
?>You can test the settings page by editing the number of links displayed and noticing the block content adjusts accordingly. Navigate to the settings page: admin/settings/onthisdate or Administer » Site configuration » On this date. If the page doesn't exist, you may have to disable and enable the module for the system to register the new settings page. Adjust the number of links and save the configuration. Notice the number of links in the block adjusts accordingly.
Validate the user input
Although we arn't required to validate the user input, it is nice need to validate values. We can do this by writing a onthisdate_admin_validate function that would check whether the value the user entered is a number greater than 0. Drupal will see that we have a _validate function, and then check the values against it.
<?php
function onthisdate_admin_validate($form, &$form_state) {
$maxdisp = $form_state['values']['onthisdate_maxdisp'];
if (!is_numeric($maxdisp)) {
form_set_error('onthisdate_maxdisp', t('You must select a number for the maximum number of links.'));
}
else if ($maxdisp <= 0) {
form_set_error('onthisdate_maxdisp', t('Maximum number of links must be positive.'));
}
}
?>Now if you try to enter something that it doesn't like (a word, or a negative number), it will tell you to enter a correct value.

May need to clear the cache to see the onthisdate settings page
if the page does not show, go to admin/settings/performance and scroll to the foot of the page and click the
Clear cached data button.
decrease $limitnum
$limitnum should be decrease for example:
--$limitnum - because this is not a count of fields but upper limit (count from zero not from one)
I don't agree
Works fine without decreasing it. Setting the value to 2 will show 2 links and that's what users would expect.
$result --> $queryResult
The new code will not work unless you change the following line in the code from section 05, too:
while ($links = db_fetch_object($result)) {Here, the variable name "$result" needs to be replaced with "$queryResult".
ahh, now I see what you mean
After pasting the code from section 5 into my module, I finished the tutorial, and found the limit clause wasn't working. No matter what I set the 'onthisdate_maxdisp' variable to, in the admin form you create with the first bit of code on this page, all the links would display. Then I came back and read this page more closely. I had pasted the code from this page randomly into onthisdate.module, not reading "so we'll need to adjust our query statement in the onthisdate_block function" and understanding to paste the code in that function.
It wasn't clear to me, to remove this line:
$result = db_query("SELECT nid, title, created FROM {node} WHERE created >= '%s' AND created <= '%s'", $start_time, $end_time);
And replace it with the third code snippet on this page, and finally change this line:
while ($links = db_fetch_object($result)) {
to this:
while ($links = db_fetch_object($queryResult)) {
As the previous comment hinted. After I did that it worked as expected. Maybe seems clear to someone who understands coding better, but I didn't get it at first.
Yep either that...
Or change the new variable back to the old name '$result'. Maybe leave it as it is though, keep people on their toes ;-)
Roger Heathcote - www.technicalbloke.com
Reset to Default
The reset to default button in the form has an interesting behavior.
If a value which doesn't validate is typed in the textfield, clicking it will attempt to validate and give the error message but will not restore the field to the default value.
Otherwise (if a valid value is present in the textfield) clicking the reset to default button will delete the onthisdate_maxdisp variable from the variables table and print in the form textfield the default value from the variable_get() function call, in effect changing the value without requiring the user to save.
You may need to uninstall and re-install your module
As referenced elsewhere[0], if your menu items aren't working, you may want to do a lookup in the menu_router table for your menu item (you can search with WHERE path='your/path'). If you see your menu item in the database as it looked before you modified it, you may need to uninstall and re-install the module.
0. http://drupal.org/node/206764#comment-897196
ctype_digit instead of is_numeric
The is_numeric should probably be replaced with ctype_digit since is_numeric can mean any number, i.e. decimals, scientific notation, ctype_digit will return false if any non-numeric character is in the string.
I.e. for some reason someone enters 0.1e2 a.k.a. 10 and is_numeric is used it would accept it as valid but the sql query would error when trying to LIMIT 0.1e2.
That validation is not complete
Ignore me I missed a comma.
Futher Clarity Needed
Forgive me in advance for English is only my second language. I have an inquiry regarding variable_get:
- It was not clear if the variable 'onthisdate_maxdisp' has to be made inserted in the variable table or would variable_get() automatically make one if it does not exists?
- Do I always need to place the two needed scripts to perform the SQL query and Variable Get in the _block hook? What if I don't need a block where can I place it? (e.g. is it ok to put it in the top of the .module file?)
Advise is highly appreciated :) thanks