Compound fields in Drupal 7

Posted Mar 1, 2011 // 26 comments
Tobby :

With Field API being part of core as of Drupal 7.0, creating custom fields just became a lot easier than they were in Drupal 6.x. Suppose you need to create a compound field, which consists of several different form elements (a select and several text fields). You can now have a basic compound field in three basic steps:

  • Define the field (info and schema)
  • Define the field form (widget)
  • Define the output (formatter)

Note: Much of what will be discussed here can be found in the Field Example module, but with some much-needed explanations.

Step 1: Tell Drupal about the field

As with most APIs in Drupal, there are hooks that are required in order to tell Drupal about what it is you're creating. The hooks you'll need to initially define your field are:

  • hook_field_info() -- The initial hook that defines which widget and formatter elements to use for the field
  • hook_field_schema() -- The schema for storing your field information. This is similar to hook_schema() for modules, and also belongs in your module's .info file.
  • hook_field_validate() -- A validation function that will validate all input from the field forms.
  • hook_field_is_empty() -- Determine if your field has data, so that you may or may not be able to modify the widget later.

hook_field_info()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function dnd_fields_field_info() {
  return array(
    'dnd_fields_ability' => array(
      'label' => t('D&D ability'),
      'description' => t("This field stores PC ability scores"),
      'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),
      'default_widget' => 'dnd_fields_ability',
      'default_formatter' => 'dnd_fields_ability', // This doesn't *have* to be the same name as default_widget's value, this is only coincidence
    ),
    'dnd_fields_skill' => array(
      'label' => t('D&D skill'),
      'description' => t("This field stores PC skill values"),
      'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),
      'default_widget' => 'dnd_fields_skill',
      'default_formatter' => 'dnd_fields_skill',
    ),
 
  );
}

This hook defines the initial widget. It defines the widget and formatter internally, but it also contains what will be displayed in the Manage Fields page of your content type admin UI. The default_widget and default_formatter aren't functions, they are operations that will be passed to hooks later. The settings element will let you set additional (optional) functions to handle default values, which may come in handy if you want the values of a select field to come from your database.

hook_field_schema()

Note: This goes in your .install file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function dnd_fields_field_schema($field) {
  switch($field['type']) {
    case 'dnd_fields_ability':
      $columns = array(
        'ability' => array(
          'type' => 'varchar',
          'length' => '32',
          'not null' => FALSE,
        ),
        'score' => array(
          'type' => 'int',
          'size' => 'small',
          'not null' => TRUE,
          'default' => 0,
        ),
        'mod' => array(
          'type' => 'int',
          'size' => 'small',
          'not null' => TRUE,
          'default' => 0,
        ),
        'tempscore' => array(
          'type' => 'int',
          'size' => 'small',
          'not null' => TRUE,
          'default' => 0,
        ),
        'tempmod' => array(
          'type' => 'int',
          'size' => 'small',
          'not null' => TRUE,
          'default' => 0,
        ),
      );
      break;
    case 'dnd_fields_skill':
      $columns = array(
        'skill' => array(
          'type' => 'varchar',
          'length' => '128',
          'not null' => FALSE,
        ),
        'ranks' => array(
          'type' => 'int',
          'size' => 'small',
          'not null' => TRUE,
          'default' => 0,
        ),
      );
      $indexes = array(
        'skill' => array('skill'),
      );
      break;
  }
  return array(
    'columns' => $columns,
    'indexes' => $indexes,
  );
}

This is identical to hook_schema() in the sense that syntax and data types are the same. For each field element in your widget, you will need a corresponding column (unless of course you are combining data from multiple input fields).

hook_field_validate()

1
2
3
4
5
function dnd_fields_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  foreach ($items as $delta => $item) {
    // ...
  }
}

This is a required hook in order for your field to work, but it is not imperative in the sense that it defines content or functionality. It is, however, strongly advisable that you spend a good deal of time validating your user input. To demonstrate it here, however, could conceivably be another blog post.

hook_field_is_empty()

1
2
3
4
5
function dnd_fields_field_is_empty($item, $field) {
  $temp = array_keys($field['columns']);
  $key = array_shift($temp);
  return empty($item[$key]);
}

In the simplest form, this hook tells Drupal that your field is or is not empty. This is checked when a site administrator attempts to modify an existing widget. In cases when there is existing field data, it is unadvisable to modify field settings, such as the list of allowed values (since the content in your existing fields will not be re-validated).

Step 2: Create the field widget

The next step is to create the field widget. This is essentially the form (using Form API) for user input as well as any interface elements that you wish to create. The hooks needed to create the field widget are:

hook_field_widget_info()

This hook tells Drupal enough information about your widget in order to start using the field and to accept user input.

1
2
3
4
5
6
7
8
9
10
11
12
function dnd_fields_field_widget_info() {
  return array(
    'dnd_fields_ability' => array(
      'label' => t('D&D ability score'),
      'field types' => array('dnd_fields_ability'),
    ),
    'dnd_fields_skill' => array(
      'label' => t('D&D skill values'),
      'field types' => array('dnd_fields_skill'),
    ),
  );
}

At minimum, this returns a label that is displayed when selecting this widget from the Manage fields page while editing a content type. It also ties together that label to the field types, which are defined next.

hook_field_widget_form()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
function dnd_fields_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  switch ($instance['widget']['type']) {
    case 'dnd_fields_ability':
      $settings = $form_state['field'][$instance['field_name']][$langcode]['field']['settings'];
 
      $fields = array(
        'ability' => t('Ability'),
        'score' => t('Score'),
        'mod' => t('Modifier'),
        'tempscore' => t('Temp score'),
        'tempmod' => t('Temp modifier'),
      );
 
      $abilities = (!empty($field['settings']['abilities'])) ? explode("\n", $field['settings']['abilities']) : array();
 
      foreach ($fields as $key => $label) {
        $value = isset($items[$delta][$key]) ? $items[$delta][$key] : '';
        if (empty($value) && $key == 'ability') {
          $value = $abilities[$delta];
        }
 
        $element[$key] = array(
          '#attributes' => array('class' => array('edit-dnd-fields-ability'), 'title' => t(''), 'rel' => strtolower($abilities[$delta])),
          '#type' => 'textfield',
          '#size' => 3,
          '#maxlength' => 3,
          '#title' => $label,
          '#default_value' => $value,
          '#attached' => array(
            'css' => array(drupal_get_path('module', 'dnd_fields') . '/dnd_fields.css'),
            'js' => array(drupal_get_path('module', 'dnd_fields') . '/dnd_fields.js'),
            ),
          '#prefix' => '<div class="dnd-fields-ability-field dnd-fields-ability-' . $key . '-field dnd-fields-ability-' . $key . '-' . strtolower($abilities[$delta]) . '-field">',
          '#suffix' => '</div>',
        );
        if ($key == 'ability') {
          $element[$key]['#size'] = 10;
          $element[$key]['#maxlength'] = 32;
          if (arg(0) != 'admin') {
            $element[$key]['#attributes'] = array('readonly' => 'readonly');
          }
        }
      }
      break;
    case 'dnd_fields_skill':
      $settings = $form_state['field'][$instance['field_name']][$langcode]['field']['settings'];
        
      // Get the list of skills broken into an array, and split those elements into a 
      // multi-dimensional arrays
      $skills_temp = (!empty($settings['skill'])) ? preg_split('/(\r\n?|\n)/', $settings['skill']) : array();
      $skills = array(0 => t('-Choose a skill-'));
      foreach ($skills_temp as $skill) {
        if(strpos($skill, '|') === FALSE) {
          $skills[] = array($skill);
        }
        else {
          $temp = explode('|', $skill);
          $skills[$temp[0]] = $temp[1];
        }
      }
          
      $element['skill'] = array(
        '#attributes' => array('class' => array('edit-dnd-fields-skill'), 'title' => t('')),
        '#type' => 'select',   
        '#options' => $skills,
        '#title' => t('Skill name'),
        '#description' => t('Choose a skill you wish to allocate ranks to.'),
        '#attached' => array(
          'css' => array(drupal_get_path('module', 'dnd_fields') . '/dnd_fields.css'),
          'js' => array(drupal_get_path('module', 'dnd_fields') . '/dnd_fields.js'),
        ),
        '#prefix' => '<div class="dnd-fields-skill-field dnd-fields-skill-skill-field">',
        '#suffix' => '</div>',
      );
      $element['ranks'] = array(
        '#attributes' => array('class' => array('edit-dnd-fields-ranks'), 'title' => t('')),
        '#type' => 'textfield',
        '#size' => 3,
        '#maxlength' => 3,
        '#title' => t('Skill ranks'),
        '#prefix' => '<div class="dnd-fields-skill-field dnd-fields-skill-ranks-field">',
        '#suffix' => '</div>',
      );
      // Loop through all the element children and set a default value if we have one. Then set HTML wrappers.
      foreach (element_children($element) as $element_key) {
        $value = isset($items[$delta][$element_key]) ? $items[$delta][$element_key] : '';  
        $element[$element_key]['#default_value'] = $value;
      }
      break;
 
  }
  return $element;
}

With this hook, you now have something tangible that you can see and work with. This hook doubles as both the widget that the user sees when entering content into a node as well as the form the site administrator uses when creating a field with this widget on the content type. This hook is called for each field looking for widget information. In this case, we'll need to check the $instance['widget']['type'] value, which will correspond to the 'field types' element defined in our hook_field_widget_info() function above. Just as with regular forms using Form API, there is a $form_state variable that contains information that we want about the current state of the form widget. This is important for instances where a site administrator creates the field with default data. It also helps when doing multi-step forms with extremely complex widgets (yes, multi-step forms are possible, but those are a blog post of their own). Everything else is just Forms API. Feel free to embellish the form fields with jQuery plugins for a nice interface. The #attached array element provides an easy way to add JavaScript and CSS files to your form. You can also add #prefix and #suffix elements to provide HTML wrappers, which we will use later in the formatter hooks below.

Step 3: Format the field

Perhaps the most satisfying aspect of creating a custom field is the formatter. This is the output displayed to the end user. Just like with the widget hooks, there are two simple formatter hooks that we will use:

hook_field_formatter_info()

1
2
3
4
5
6
7
8
9
10
11
12
function dnd_fields_field_formatter_info() {
  return array(
    'dnd_fields_ability' => array(
      'label' => t('Ability scores'),
      'field types' => array('dnd_fields_ability'),
    ),
    'dnd_fields_skill' => array(
      'label' => t('Skill fields'),
      'field types' => array('dnd_fields_skill'),
    ),
  );
}  

Again, this is simply a list of the label for your field and which field types that are involved in the next hook.

hook_field_formatter_view()

This is the hook that will format and display the field.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function dnd_fields_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
 
  switch ($display['type']) {
    case 'dnd_fields_ability':
      $headers = array(
        t('Skill'),
        t('Score'),
        t('Modifier'),
        t('Temp score'),
        t('Temp modifier'),
      );
 
      $element[0]['#theme'] = 'table';
      $element[0]['#data'] = array('header' => $headers, 'rows' => $items);
      break;
 
    case 'dnd_fields_skill':
      // Set the skill name to the human readable name instead of the internal machine name.
      $skills = dnd_fields_skill_list_array(explode("\n", $field['settings']['skill']));
      foreach ($items as $delta => $item) {
        $items[$delta]['skill'] = $skills[$item['skill']];
      }
 
      $headers = array(
        t('Skill'),
        t('Ranks'),
      );
 
      $element[0]['#theme'] = 'table';
      $element[0]['#data'] = array('header' => $headers, 'rows' => $items);
 
      break;
  }
  return $element;
}

There are countless ways to format the field data. For simplicity's sake, I'll water this down to the bare minimum needed to display the field data in an HTML table. Just like the hook_field_widget_form hook above, this looks at the display type to determine how to format the field. This hook returns a renderable array. In the example above, a simple table is used, which prints multiple entries as part of the same table. In other cases, you may want to loop through each field item in order to theme it individually.

1
2
3
4
5
6
foreach ($items as $delta => $item) {
  $element[$delta] = array(
    '#theme' => 'some_theme_function', 
    '#data' => $item['value'], 
  );
}

In the original example above, $element[0] is the first instance of $element[$delta].

Final thoughts

While a field requires eight hooks to get started, most of it is straightforward. This example focuses mainly compound fields, but many other options are possible. For another good example, check out the Field Example module that comes with core. That module uses a jQuery color picker to help with field input. There are other hooks worth considering:

  • hook_field_presave() - When dealing with compound fields, it's important to set any values for form elements that weren't set by the user. This will help prevent SQL errors when inserting null values into database tables that have 'not null' set.
  • hook_form_field_ui_field_edit_form_alter() - This is hook_form_FORM_ID_alter(). This is a really useful hook for manipulating existing field forms, and adding default values that have to be calculated.

In the widget form hook above, I alluded to adding some HTML wrappers to help with formatting the form. By default, all the form fields will be displayed one on top of each other. Using this CSS, you can make forms that look a little more cohesive:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.field-type-dnd-fields-skill {
  display: inline-block;
  width: 100%;
}
  .dnd-fields-skill-field {
    float: left;
    margin: 0 10px 0 0;
  }
    .dnd-fields-skill-ranks-field .form-text {
      width: 52px;
    }
    .dnd-fields-skill-field label {
      font-weight: normal;
      font-size: 12px;
      padding: 0 0 2px;
    }
    .dnd-fields-skill-ranks-field .form-item .description {
      width: 200px;
      display: none;
    }

About Tobby

As one of our superb Team Architects, Tobby Hagler expertise spans the gamut of technical capabilities -- from interface development to application layer engineering and system architecture.

Tobby’s specialties tend to focus on ...

more >

Read Tobby 's Blog

Comments

by Anonymous (not verified) on Tue, 03/01/2011 - 16:21

How does this compare to

How does this compare to http://drupal.org/project/field_collection? I Look forward to trying to implement the technique you outlined. Thanks!

by ipwa (not verified) on Fri, 04/29/2011 - 12:24

With field collection you

With field collection you need to make a node, and then you can add your 'collected' field (you'll see a link where the field should be). With this approach you can add the 'compound' field while creating the node.

by J (not verified) on Mon, 03/07/2011 - 18:49

Good Description but I have some problems implementing

Hello, I definitely need to implement a compound input. I am trying trying to create a large form with sections where the form will do some dynamic processing while the user is completing the form.

For example, in one section, the user needs to list as many insurance policies as the currently have. For example, some users may have 1 insurance policy and others have 5. Within the section, the users would answer 2-3 questions (e.g. insurance policy name, value, number of years, etc.). Then they need to be able to "Add Another" which would save that entry area and add another line of inputs.

All of this would still be within one node.

I think a compound entry would possible work. What do you think? I tried to implement your code to get an example running, but I was unable to because I cannot figure out what text files (e.g. install, module, info, to put your code into.

Any ideas help,

Thanks, J

by Tobby on Wed, 03/09/2011 - 16:55

It sounds like a compound

It sounds like a compound field is definitely what you need. The idea is that this is really 2 or 3 or 4 fields that get clumped together into a single "field" and stored in a single record in that field's database table.

As far as which files to use: The hook_fields_schema function needs to go in your .install file. Everything else will go in your .module file. You won't need to define anything special in your module's .info file to make Drupal aware of any additional field data or files.

by Stefan (not verified) on Fri, 04/01/2011 - 14:46

Add Another

Hi, thank you for your great article. I Have the same Question: e.g. for a recipe site I need a compund field for ingredients:

100 | g | Butter 0.5 | L | Milk Add another Button (click)

100 | g | Butter 0.5 | L | Milk 250 | g | Sugar Add another Button

Could you give us a hint how to implement this?

by Tobby on Mon, 04/04/2011 - 13:50

Once you have your compound

Once you have your compound field, it's easy to add the "add another" button. When you create your content type, and add your ingredient field, you'll have the option to limit how many entries can be added -- choose "Infinite" and users will be able to create new fields as needed.

by Anonymous (not verified) on Wed, 07/13/2011 - 05:38

How to delete an item ?

When you set a number of values for the field in your content type, how to add a button to delete only an item ?

by Anonymous (not verified) on Wed, 03/09/2011 - 15:35

Great Article....but errors

Tobby, I have been trying to use your article to create compound fields. I actually have reviewed the rGB example within Field Examples, however, in the database it still concatenates the three values into one. For my site, I am creating a form with three seperate values that do not need concatenated. For example. Car Make, Year, Color.

the reaason I am trying to use multiple values for one field is because I need to allow the user to have the ability to create multiple cars and in Drupal 7 it sounds like the multigroup feature which was in Drupal 6 CCK is not available. I reviewed D7 Field Group module but it is not going to address multigroup. I cannot use Field Collection either because it asks addition information after original posting.

Anyway, so I thought I would try to create a compound entry, then select "add more values" on the field (set to unlimited)

So, if you have any thoughts on how I can do this, please let me know.

Meanwhile, the errors I am receiving: Parse error: syntax error, unexpected T_STRING, expecting T_VARIABLE in the following line.

function dnd_fields_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {

Then I have this error as well: Notice: Undefined offset: 0 in the following lines:

$value = $abilities[$delta]; (this is within the foreach ($fields as $key => $label) within the function dnd_fields_field_widget_form.

'#attributes' => array('class' => array('edit-dnd-fields-ability'), 'title' => t(''), 'rel' => strtolower($abilities[$delta])),

'#prefix' => '<div class="dnd-fields-ability-field dnd-fields-ability-' . $key . '-field dnd-fields-ability-' . $key . '-' . strtolower($abilities[$delta]) . '-field">',

And then: Fatal error: Call to undefined function dnd_fields_skill_list_array()

Code: $skills = dnd_fields_skill_list_array(explode("\n", $field['settings']['skill']));

Also, in my content type the ability drop down is not populating at all and the skill drop down is also not populating.

by Tobby on Wed, 03/09/2011 - 16:52

There are two errors that

There are two errors that you'll get with this code: 1) there was a formatting issue with the ampersand in function dnd_fields_field_validate. That should be &$errors and not &errors. 2) This code is taken from a larger module, and I didn't include a large helper function that I used called dnd_fields_skill_list_array(). Instead, you can simple set the $skills variable to an array of your choice (like $skills = array()).

That would likely be the cause of the 2 dropdowns not populating; the default values in my sample code are using additional functions that aren't included.

I'll update the sample code above to remove those errors.

For the complete code, check out the dnd_fields module that comes with http://drupal.org/project/dnd_character.

by drikc (not verified) on Thu, 04/07/2011 - 09:22

hook_field_is_empty()

Hello, thank you for this article.

"hook_field_is_empty(): Determine if your field has data, so that you may or may not be able to modify the widget later."

Are you sure of this?

I thought that it permit to say if a submitted field value is empty. If it's the case it will delete the row field data (if it exists) in the database. Well that's what I've notice.

Also, looking at your implementation of hook_field_is_empty() what I see is that you check only the first column value of your field to tel that the thole field is empty.

by Desmond (not verified) on Thu, 05/19/2011 - 16:08

Thanks for the great article?

Thanks for the great article? Any insight on getting this working with file fields?

by Anonymous (not verified) on Wed, 06/22/2011 - 07:33

Great article! It has helped

Great article! It has helped me to construct my compound field in way more little time.

Just one question: are there any best practices about the task of deleting single element when this field is configured as having multiple values?

As an example I tried to consider file_field module, but there is nothing so clear as here.

Thanks.

by Ben (not verified) on Thu, 06/30/2011 - 12:18

Thanks for the great writeup.

Thanks for the great writeup. One correction, your hook_field_schema is missing a return value.

by Tobby on Sat, 07/02/2011 - 09:10

Thanks for pointing that out

Thanks for pointing that out Ben. I've updated the sample code above.

by Owen (not verified) on Fri, 07/22/2011 - 11:37

Switch fall-through

Is that an intentional fall-through in your hook_field_schema switch?

by Tobby on Mon, 08/08/2011 - 08:11

No, that wasn't intentional.

No, that wasn't intentional. Much of the sample code above was taken from a much larger module, and so I've missed a few things in the copy/paste of sample code for use in this blog post. Thanks for pointing that out -- I've updated the sample above to properly break the switch block.

by Kevin (not verified) on Thu, 08/11/2011 - 07:12

Using date fields

Hello Tobby,

Very nice job. I'd like to use this approach with Date fields and date popoup widget (understand you have only textfields). Could you tell me how this should be done?

by Anonymous (not verified) on Fri, 08/19/2011 - 00:27

All empty values

Great tutoria!

I'm getting an sql error when I have an empty decimal(number) value. How do I allow for empty values to be NULL? Actual error is "Incorrect decimal value." In my insert file, I have 'not null' => FALSE;

Second question, where would be a good place to sanitize the number field (take out commas, dollar signs, etc.)?

by Prateek (not verified) on Fri, 09/02/2011 - 18:17

How to save compound fields with node_save API

Hi,

Thanks for sharing this article. But can you tell me how to save compound fields from node_save API.

by samundra.shr@gmail.com (not verified) on Sat, 10/22/2011 - 16:35

Any idea how can I implement the default #type=image field api

Any idea on how can I implement the default #type=image in my own compound field that I am creating. I am attaching these compounds fields dynamically to node using field_create_field($field). But I am just confused in the use of image in my compound element.

I have borrowed most of the code form metatags_quick module which also uses compund fields and its own formatter view.

by RumpledElf (not verified) on Wed, 04/18/2012 - 18:08

Images

Ah, this is the comment that brought me here. I'd also love to know how to make a compound field that has an image in it.

Compound fields are fantastically useful but surprisingly infrequently used.

by Tobby on Thu, 04/19/2012 - 12:53

Creating a compound field

Creating a compound field widget with a file upload element starts just like adding any other field type (select, text field, etc.).

You'll start by defining your schema in hook_field_schema() to use an FID as the value, then define your field widget form in hook_field_widget_form() to use the standard file widget ( http://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7#file ).

Since file management in Drupal forms requires a few extra steps (validation and submit handlers to handle the creation of the file object, etc), you'll need to read up on file management separately, since that's really another matter entirely. Try http://blog.ryantan.net/2011/04/drupal-7-handling-file-uploads .

That's the largest significant difference in handling files as opposed to other types of data in a compound field. You need a way to manage the upload, which is done the same way as if it were a regular field.

Finally, in your field formatter, you'll need some way to display the file data (link to the file, or render a thumbnail, for instance).

by Josh Beauregard (not verified) on Mon, 11/28/2011 - 14:56

thanks

It took a while but I got it down thanks so much for the write up

by Josh Beauregard (not verified) on Wed, 11/30/2011 - 12:53

Follow up idea

thanx for the article, a great follow up article would be, how to then populate the custom field via feeds.

by Slava (not verified) on Mon, 12/05/2011 - 11:50

Hi Tobby, thanks for this

Hi Tobby, thanks for this article. However, I'd suggest to split up field and widget/formatter names in dnd_fields_field_info() - because they might confuse during next steps - for instance, it is not clear what to use in hook_field_schema() in $field['type'] variable - field,widget or formatter name? So it would be great if you leave field name intact, but rename widget and formatter to dnd_fields_ability_widget and dnd_fields_ability_formatter respectively.

Many thanks.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
  • Allowed HTML tags: <a> <strong> <code> <p> <img> <ul> <ol> <li> <h2> <h3> <h4> <b> <u> <i>
  • You may insert videos with [video:URL]

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.