diff --git a/includes/entity.property_ui.inc b/includes/entity.property_ui.inc
new file mode 100644
index 0000000..15a16fb
--- /dev/null
+++ b/includes/entity.property_ui.inc
@@ -0,0 +1,370 @@
+<?php
+
+/**
+ * @file
+ * Provides form elements for entity properties.
+ */
+
+/**
+ * Implements hook_element_info().
+ */
+function entity_element_info() {
+  // A duration form element.
+  $types['entity_property_ui_duration'] = array(
+    '#input' => TRUE,
+    '#tree' => TRUE,
+    '#default_value' => 0,
+    '#value_callback' => 'entity_property_ui_element_duration_value',
+    '#process' => array('entity_property_ui_element_duration_process', 'ajax_process_form'),
+    '#after_build' => array('entity_property_ui_element_duration_after_build'),
+    '#pre_render' => array('form_pre_render_conditional_form_element'),
+  );
+  return $types;
+}
+
+/**
+ * FAPI process callback for the duration element type.
+ */
+function entity_property_ui_element_duration_process($element, &$form_state) {
+  $element['value'] = array(
+    '#type' => 'textfield',
+    '#size' => 8,
+    '#element_validate' => array('entity_property_ui_integer_validate'),
+    '#default_value' => $element['#default_value'],
+    '#required' => !empty($element['#required']),
+  );
+  $element['multiplier'] = array(
+    '#type' => 'select',
+    '#options' => entity_property_ui_element_duration_multipliers(),
+    '#default_value' => 1,
+  );
+
+  // Put the child elements in a container-inline div.
+  $element['value']['#prefix'] = '<div class="rules-duration container-inline">';
+  $element['multiplier']['#suffix'] = '</div>';
+
+  // Set an appropriate multiplier.
+  if (!empty($element['value']['#default_value'])) {
+    foreach (array_keys(entity_property_ui_element_duration_multipliers()) as $m) {
+      if ($element['value']['#default_value'] % $m == 0) {
+        $element['multiplier']['#default_value'] = $m;
+      }
+    }
+    // Divide value by the multiplier, so the display is correct.
+    $element['value']['#default_value'] /= $element['multiplier']['#default_value'];
+  }
+  return $element;
+}
+
+/**
+ * Defines possible duration multiplier.
+ */
+function entity_property_ui_element_duration_multipliers() {
+  return array(
+    1 => t('seconds'),
+    60 => t('minutes'),
+    3600 => t('hours'),
+    86400 => t('days'),
+  );
+}
+
+/**
+ * Helper function to determine the value for a rules duration form
+ * element.
+ */
+function entity_property_ui_element_duration_value($element, $input = FALSE) {
+  // This runs before child elements are processed, so we cannot calculate the
+  // value here. But we have to make sure the value is an array, so the form
+  // API is able to proccess the children to set their values in the array. Thus
+  // once the form API has finished processing the element, the value is an
+  // array containing the child element values. Then finally the after build
+  // callback converts it back to the numeric value and sets that.
+  return array();
+}
+
+/**
+ * FAPI after build callback for the duration parameter type form.
+ * Fixes up the form value by applying the multiplier.
+ */
+function entity_property_ui_element_duration_after_build($element, &$form_state) {
+  if ($element['value']['#value'] !== '') {
+    $element['#value'] = $element['value']['#value'] * $element['multiplier']['#value'];
+    form_set_value($element, $element['#value'], $form_state);
+  }
+  else {
+    $element['#value'] = NULL;
+    form_set_value($element, NULL, $form_state);
+  }
+  return $element;
+}
+
+function entity_property_ui_text($name, $info) {
+  if (!empty($info['options list'])) {
+    $form = array(
+      '#type' => 'select',
+      '#options' => $info['options list']($name, $info),
+    );
+  }
+  else {
+    $form = array(
+      '#type' => 'textarea',
+    );
+  }
+  $settings = array($name => isset($info['default value']) ? $info['default value'] : NULL);
+  $form[$name] += array(
+    '#title' => t('Value'),
+    '#default_value' => $settings[$name],
+    '#required' => empty($info['optional']),
+    '#after_build' => array('entity_property_ui_text_after_build'),
+    '#rows' => 3,
+  );
+  return $form;
+}
+
+/**
+ * FAPI after build callback to ensure empty form elements result in no value.
+ */
+function entity_property_ui_text_after_build($element, &$form_state) {
+  if (isset($element['#value']) && $element['#value'] === '') {
+    $element['#value'] = NULL;
+    form_set_value($element, NULL, $form_state);
+  }
+  // Work-a-round for the text_format element.
+  elseif ($element['#type'] == 'text_format' && !isset($element['value']['#value'])) {
+    form_set_value($element, NULL, $form_state);
+  }
+  return $element;
+}
+
+function entity_property_ui_text_token($name, $info) {
+  $form = entity_property_ui_text($name, $info);
+  $form[$name]['#element_validate'][] = 'entity_property_ui_text_token_validate';
+  $form[$name]['#description'] = t('May only contain lowercase letters, numbers, and underscores and has to start with a letter.');
+  $form[$name]['#rows'] = 1;
+  return $form;
+}
+
+/**
+ * FAPI callback to validate a token.
+ */
+function entity_property_ui_text_token_validate($element) {
+  $value = $element['#value'];
+  if (isset($value) && $value !== '' && !entity_property_verify_data_type($value, 'token')) {
+    form_error($element, t('%name may only contain lowercase letters, numbers, and underscores and has to start with a letter.', array('%name' => isset($element['#title']) ? $element['#title'] : t('Element'))));
+  }
+}
+
+function entity_property_ui_text_formatted($name, $info) {
+  $form = entity_property_ui_text($name, $info);
+  $settings += array($name => isset($info['default value']) ? $info['default value'] : array('value' => NULL, 'format' => NULL));
+
+  $form[$name]['#type'] = 'text_format';
+  $form[$name]['#base_type'] = 'textarea';
+  $form[$name]['#default_value'] = $settings[$name]['value'];
+  $form[$name]['#format'] = $settings[$name]['format'];
+  return $form;
+}
+
+function entity_property_ui_decimal($name, $info) {
+  $form = entity_property_ui_text($name, $info);
+  if (empty($info['options list'])) {
+    $form[$name]['#type'] = 'textfield';
+  }
+  $form[$name]['#element_validate'][] = 'entity_property_ui_decimal_validate';
+  $form[$name]['#rows'] = 1;
+  return $form;
+}
+
+/**
+ * FAPI validation of a decimal element. Improved version of the private
+ * function _element_validate_number().
+ */
+function entity_property_ui_decimal_validate($element, &$form_state) {
+  // Substitute the decimal separator ",".
+  $value = strtr($element['#value'], ',', '.');
+  if ($value != '' && !is_numeric($value)) {
+    form_error($element, t('%name must be a number.', array('%name' => $element['#title'])));
+  }
+  elseif ($value != $element['#value']) {
+    form_set_value($element, $value, $form_state);
+  }
+}
+
+function entity_property_ui_integer($name, $info) {
+  $form = entity_property_ui_text($name, $info);
+  if (empty($info['options list'])) {
+    $form[$name]['#type'] = 'textfield';
+  }
+  $form[$name]['#element_validate'][] = 'entity_property_ui_integer_validate';
+  return $form;
+}
+
+/**
+ * FAPI validation of an integer element. Copy of the private function
+ * _element_validate_integer().
+ */
+function entity_property_ui_integer_validate($element, &$form_state) {;
+  $value = $element['#value'];
+  if (isset($value) && $value !== '' && (!is_numeric($value) || intval($value) != $value)) {
+    form_error($element, t('%name must be an integer value.', array('%name' => isset($element['#title']) ? $element['#title'] : t('Element'))));
+  }
+}
+
+function entity_property_ui_boolean($name, $info) {
+  $settings += array($name => isset($info['default value']) ? $info['default value'] : NULL);
+  // Note: Due to the checkbox even optional parameter always receive a value.
+  $form = array(
+    '#type' => 'checkbox',
+    '#title' => check_plain($info['label']),
+    '#default_value' => $settings[$name],
+  );
+  return $form;
+}
+
+function entity_property_ui_date($name, $info) {
+  $settings = array($name => isset($info['default value']) ? $info['default value'] : (empty($info['optional']) ? gmdate('Y-m-d H:i:s', time()) : NULL));
+
+  // Convert any configured timestamp into a readable format.
+  if (is_numeric($settings[$name])) {
+    $settings[$name] = gmdate('Y-m-d H:i:s', $settings[$name]);
+  }
+  $form = entity_property_ui_text($name, $info);
+  $form[$name]['#type'] = 'textfield';
+  $form[$name]['#element_validate'][] = 'entity_property_ui_date_validate';
+  // Note that the date input evaluator takes care for parsing dates using
+  // strtotime() into a timestamp, which is the internal date format.
+  $form[$name]['#description'] = t('The date in GMT. Format: %format or other values in GMT known by the PHP !strtotime function like "+1 day". Relative dates like "+1 day" or "now" relate to the evaluation time.', array('%format' => gmdate('Y-m-d H:i:s', time() + 86400), '!strtotime' => l('strtotime()', 'http://php.net/strtotime')));
+
+  //TODO: Leverage the jquery datepicker+timepicker once a module providing
+  //the timpeicker is available.
+  return $form;
+}
+
+/**
+ * FAPI validation of a date element. Makes sure the specified date format is
+ * correct and converts date values specifiy a fixed (= non relative) date to
+ * a timestamp. Relative dates are handled by the date input evaluator.
+ */
+function entity_property_ui_date_validate($element, &$form_state) {
+  $value = $element['#value'];
+  if ($value == '' || (is_numeric($value) && intval($value) == $value)) {
+    // The value is a timestamp.
+    return;
+  }
+  elseif (is_string($value) && strtotime($value, time()) === FALSE) {
+    form_error($element, t('Wrong date format. Specify the date in the format %format.', array('%format' => gmdate('Y-m-d H:i:s', time() + 86400))));
+  }
+  elseif (is_string($value) && _entity_property_ui_date_is_fixed($value)) {
+    // As the date string specifies a fixed format, we can convert it now.
+    $value = strtotime($value, time());
+    form_set_value($element, $value, $form_state);
+  }
+}
+
+/**
+ * Determine whether the given date string specifies a fixed date.
+ */
+function _entity_property_ui_date_is_fixed($date) {
+  return is_string($date) && preg_match('/^(\d{4})-?(\d{2})-?(\d{2})([T\s]?(\d{2}):?(\d{2}):?(\d{2})?)?$/', $date);
+}
+
+function entity_property_ui_duration($name, $info) {
+  $form = entity_property_ui_text($name, $info);
+  $form[$name]['#type'] = 'entity_property_ui_duration';
+  // @fago Why do we need this if it's already defined in entity_element_info()?
+  $form[$name]['#after_build'][] = 'entity_property_ui_element_duration_after_build';
+  return $form;
+}
+
+function entity_property_ui_uri($name, $info) {
+  $form = entity_property_ui_text($name, $info);
+  $form[$name]['#rows'] = 1;
+  $form[$name]['#description'] = t('You may enter relative URLs like %url as well as absolute URLs like %absolute-url.', array('%url' => 'user/login?destination=node', '%absolute-url' => 'http://drupal.org'));
+  return $form;
+}
+
+function entity_property_ui_list_text($name, $info) {
+  $settings += array($name => isset($info['default value']) ? $info['default value'] : NULL);
+  $form = entity_property_ui_text($name, $info);
+
+  if ($form[$name]['#type'] == 'textarea') {
+    // Fix up the value to be an array during after build.
+    $form[$name]['#after_build'][] = 'entity_property_ui_list_text_after_build';
+    $form[$name]['#pre_render'][] = 'entity_property_ui_list_text_pre_render';
+    $form[$name]['#default_value'] = implode("\n", $settings[$name]);
+    $form[$name]['#description'] = t('A list of values, one on each line.');
+  }
+  else {
+    $form[$name]['#multiple'] = TRUE;
+  }
+  return $form;
+}
+
+/**
+ * FAPI after build callback for specifying a list of values.
+ *
+ * Turns the textual value in an array by splitting the text in chunks using the
+ * delimiter set at $element['#delimiter'].
+ */
+function entity_property_ui_list_text_after_build($element, &$form_state) {
+  $element['#value'] = $element['#value'] ? explode($element['#delimiter'], $element['#value']) : array();
+  $element['#value'] = array_map('trim', $element['#value']);
+  form_set_value($element, $element['#value'], $form_state);
+  return $element;
+}
+
+/**
+ * FAPI pre render callback. Turns the value back to a string for rendering.
+ *
+ * @see entity_property_ui_list_text_after_build()
+ */
+function entity_property_ui_list_text_pre_render($element) {
+  $element['#value'] = implode($element['#delimiter'], $element['#value']);
+  return $element;
+}
+
+function entity_property_ui_list_integer($name, $info) {
+  $settings += array($name => isset($info['default value']) ? $info['default value'] : NULL);
+  $form = entity_property_ui_list_text($name, $info);
+
+  if ($form[$name]['#type'] == 'textarea') {
+    $form[$name]['#description'] = t('A list of integers, separated by commas. E.g. enter "1, 2, 3".');
+    $form[$name]['#delimiter'] = ',';
+    $form[$name]['#default_value'] = implode(", ", $settings[$name]);
+    $form[$name]['#element_validate'][] = 'entity_property_ui_list_integer_validate';
+    $form[$name]['#rows'] = 1;
+  }
+  return $form;
+}
+
+/**
+ * FAPI callback to validate a list of integers.
+ */
+function entity_property_ui_list_integer_validate($element, &$form_state) {
+  foreach ($element['#value'] as $value) {
+    if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) {
+      form_error($element, t('Each value must be an integer.'));
+    }
+  }
+}
+
+function entity_property_ui_list_token($name, $info) {
+  $form = parent::inputForm($name, $info, $settings, $element);
+
+  if ($form[$name]['#type'] == 'textarea') {
+    $form[$name]['#description'] = t('A list of text tokens, separated by commas. E.g. enter "one, two, three".');
+    $form[$name]['#element_validate'] = array('entity_property_ui_list_token_validate');
+  }
+  return $form;
+}
+
+/**
+ * FAPI callback to validate a list of tokens.
+ */
+function entity_property_ui_list_token_validate($element, &$form_state) {
+  foreach ($element['#value'] as $value) {
+    if ($value !== '' && !entity_property_verify_data_type($value, 'token')) {
+      form_error($element, t('Each value may only contain lowercase letters, numbers, and underscores and has to start with a letter.'));
+    }
+  }
+}
