diff --git a/classified.info b/classified.info
index 865d795..433cd26 100644
--- a/classified.info
+++ b/classified.info
@@ -3,12 +3,16 @@ description = "A classified ads service"
 package = Classified
 configure = admin/config/content/classified
 
+dependencies[] = xautoload
 dependencies[] = field
 dependencies[] = taxonomy
 
 ; modr8 is available on D7, but not tested
 suggests[] = modr8
 
+; Class files are located in D8 PSR-0 compatible paths 
+files[] = lib/Drupal/ed_classified/ExpiresField.inc
+
 core = 7.x
 php = 5.2
 
@@ -19,4 +23,4 @@ files[] = tests/classified_notifications.test
 ; CTools plugins are not normally declared in the .info, but when included by
 ; a context_registry_alter(), ordering prevents proper plugin detection in 
 ; CTools, so the file is declared directly to the core autoloader.
-files[] = plugins/classified_context_condition_path.inc
+; files[] = plugins/classified_context_condition_path.inc
diff --git a/classified.install b/classified.install
index 9eaf67f..d660639 100644
--- a/classified.install
+++ b/classified.install
@@ -1,13 +1,16 @@
 <?php
+use Drupal\classified\ExpiresField;
+
 /**
  * @file
  * Install file for the Classified Ads module.
  *
- * @copyright (c) 2010-2011 Ouest Systemes Informatiques (OSInet)
+ * @copyright (c) 2010-2013 Ouest Systemes Informatiques (OSInet)
  *
  * @license General Public License version 2 or later
  *
- * New code implementing a feature set derived from the ed_classified module.
+ * Original code implementing a feature set derived from ed_classified for
+ * Drupal 4.7 to 6.
  *
  * ---- Information about ed_classified ----
  *
@@ -25,6 +28,20 @@
  * ---- /ed-classified ----
  */
 
+// Autoloading does not work during module install, so we need to include files manually.
+require_once __DIR__ . '/lib/Drupal/classified/ExpiresField.php';
+
+/**
+ * Implements hook_field_schema()
+ */
+function classified_field_schema($field) {
+  $ret = $field['type'] == ExpiresField::TYPE
+    ? ExpiresField::schema()
+    : array();
+  return $ret;
+}
+
+function __74i_____________________________________________________________() {}
 /**
  * Implements hook_install().
  */
@@ -185,53 +202,6 @@ function classified_requirements($phase) {
 }
 
 /**
- * Implements hook_schema().
- *
- * Non-translation of schema data became standard with Drupal 6.9
- * @link http://drupal.org/node/332123 @endlink
- */
-function classified_schema() {
-  $schema['classified_node'] = array(
-    'description' => 'Stores the extra information for the classified node-related type.',
-    'fields' => array(
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => 'Node nid of related node.',
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => 'Node vid (version id).',
-      ),
-      'expires' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => 'Expiration timestamp',
-      ),
-      'notify' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => 'Timestamp of latest notification',
-      ),
-    ),
-
-    'primary key' => array('nid', 'vid'),
-  );
-
-  return $schema;
-}
-
-
-/**
  * Implements hook_uninstall().
  *
  * - remove vocabulary and its terms
diff --git a/classified.module b/classified.module
index 426c5bf..12de405 100644
--- a/classified.module
+++ b/classified.module
@@ -1,13 +1,18 @@
 <?php
+
+use Drupal\classified\CategoryField;
+use Drupal\classified\ExpiresField;
+
 /**
  * @file
- * A pure D6 classified ads module inspired by the ed_classified module.
+ * A pure D7 classified ads module inspired by the ed_classified module.
  *
- * @copyright (c) 2010-2011 Ouest Systèmes Informatiques (OSInet)
+ * @copyright (c) 2010-2013 Ouest Systemes Informatiques (OSInet)
  *
  * @license General Public License version 2 or later
  *
- * Original code implementing a feature set derived from ed_classified.
+ * Original code implementing a feature set derived from ed_classified for
+ * Drupal 4.7 to 6.
  *
  * ---- Information about ed_classified ----
  * Michael Curry, Exodus Development, Inc.
@@ -24,6 +29,263 @@
  */
 
 /**
+ * Delegate a field hook implementation to its class method.
+ *
+ * @param int $field_pos
+ *   Offset of the field parameter in the caller arguments.
+ *
+ * @return mixed
+ */
+function _classified_delegate($field_pos = 0) {
+  // Preserve caller information.
+  $stack = debug_backtrace(FALSE);
+  $caller = $stack[1]['function'];
+  $args = $stack[1]['args']; // Get arguments received by caller.
+  unset($stack);
+
+  // Rename hook to method.
+  $method = str_replace(array('classified_field_', '_'), array('', ' '), $caller);
+  $method = ucwords($method);
+  $method[0] = drupal_strtolower($method[0]);
+  $method = str_replace(' ', '', $method);
+
+  // Extract field class.
+  $field = $args[$field_pos];
+  $class = _classified_get_field_class($field);
+  if (method_exists($class, $method)) {
+    $ret = call_user_func_array(array($class, $method), $args);
+  }
+  else {
+    $ret = NULL;
+  }
+
+  return $ret;
+}
+
+/**
+ * Helper for hook delegation.
+ *
+ * @param array $field
+ *   A core field array.
+ *
+ * @return string
+ *   The name of a fully qualified field class.
+ */
+function _classified_get_field_class($field) {
+  $namespace = 'Drupal\classified';
+  if ($field['type'] == 'classified_expires') {
+    $class = 'ExpiresField';
+  }
+  elseif ($field['field_name'] == 'classified_category') {
+    $class = 'CategoryField';
+  }
+  else {
+    $class = NULL;
+  }
+
+  if (!empty($class)) {
+    $class = "Drupal\\classified\\$class";
+  }
+
+  return $class;
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ *
+ * - add the 'Classified Ad' view mode to the node entity
+ */
+function classified_entity_info_alter(&$entity_info) {
+  $entity_info['node']['view modes']['classified'] = array(
+    // Was 'ad list' / t('Ad list') in Views 6.x.
+    'label' => t('Classified Ad'),
+    'custom settings' => TRUE,
+  );
+}
+
+function classified_field_attach_presave($entity_type, $entity) {
+  ExpiresField::attachPresave($entity_type, $entity);
+  // Not implemented in CategoryField
+dsm(get_defined_vars(), __FUNCTION__);
+}
+
+function classified_field_attach_submit($entity_type, $entity, $form, &$form_state) {
+dsm(get_defined_vars(), __FUNCTION__);
+}
+
+function classified_field_attach_update($entity_type, $entity) {
+  ExpiresField::attachUpdate($entity_type, $entity);
+dsm(get_defined_vars(), __FUNCTION__);
+}
+
+function classified_field_attach_validate($entity_type, $entity, &$errors) {
+  ExpiresField::attachValidate($entity_type, $entity, $errors);
+dsm(get_defined_vars(), __METHOD__);
+}
+
+/**
+ * Implements hook_field_create_field().
+ */
+function classified_field_create_field($field) {
+  _classified_delegate();
+}
+
+/**
+ * Implements hook_field_formatter_info().
+ *
+ * - Classified Ads term reference formatter links to Classified Ads pages, not
+ *   standard taxonomy pages.
+ * - classified_expires expiration formatter can be either a plain string or a
+ *   rich-format element.
+ */
+function classified_field_formatter_info() {
+  $ret = array_merge(
+    CategoryField::formatterInfo(),
+    ExpiresField::formatterInfo()
+  );
+  return $ret;
+}
+
+/**
+ * Implements hook_field_formatter_settings_form().
+ *
+ * - should Classified Ads formatter add a "title" attribute ?
+ */
+function classified_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+  $ret = _classified_delegate();
+  return $ret;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ */
+function classified_field_formatter_settings_summary($field, $instance, $view_mode) {
+  $ret = _classified_delegate();
+  return $ret;
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ */
+function classified_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+  $ret = _classified_delegate(2);
+  return $ret;
+}
+
+/**
+ * Implements hook_field_info().
+ *
+ * - Category field is implemented by taxonomy module, not here.
+ */
+function classified_field_info() {
+  $ret = ExpiresField::info();
+  return $ret;
+}
+
+/**
+ * Implements hook_field_is_empty().
+ */
+function classified_field_is_empty($item, $field) {
+  $ret = _classified_delegate(1);
+  return $ret;
+}
+
+/**
+ * Implements hook_field_load().
+ */
+function classified_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
+  _classified_delegate(2);
+}
+
+function classified_field_update_field($field, $prior_field, $has_data) {
+  _classified_delegate();
+}
+
+/**
+ * Implements hook_field_update_forbid().
+ */
+function classified_field_update_forbid($field, $prior_field, $has_data) {
+  _classified_delegate();
+}
+
+function classified_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
+  _classified_delegate(2);
+}
+
+/**
+ * Implements hook_field_widget_form().
+ */
+function classified_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
+  $ret = _classified_delegate(2);
+  return $ret;
+}
+
+/**
+ * Implements hook_field_widget_info().
+ *
+ * - Category field is implemented by taxonomy module, not here.
+ */
+function classified_field_widget_info() {
+  $ret = ExpiresField::widgetInfo();
+  return $ret;
+}
+
+/**
+ * Implements hook_help().
+ */
+function classified_help($section, $arg) {
+  switch ($section) {
+    case 'admin/help#classified':
+      $ret = t('<p>The Classified Ads modules allows users to create ads with an automatic expiration period.</p><p>If the optional classified_notifications module is enabled, warnings will be send at various points in the ad lifetime:</p><ul><li>when they reach half their scheduled lifetime</li><li>one day before they expire</li><li>upon their expiration</li><li>one day before they are deleted</li><li>and upon their deletion.</li></ul>')
+        . t('<p>This module is interfaced with Views: it exposes a specific view mode, enabling node-based Views to look like those provided by the module itself by using the "Ad list" build mode in node style; and it exposes the ad expiration date.</p>');
+      break;
+
+    case 'admin/content/node-type/classified/display/classified':
+      $ret = t('<p>This build mode appears as "Ad list" when building node Views in "node" style.</p>');
+      break;
+
+    default:
+      $ret = NULL;
+  }
+
+  return $ret;
+}
+
+/**
+ * Implements hook_node_info().
+ *
+ * @todo XXX 2011-08-04 FGM: note in D6 version to check
+ *   "match nodeapi $node->type for spam module to add spam reporting links"
+ */
+function classified_node_info() {
+  $ret = array(
+    'classified' => array(
+      'name' => t('Classified Ad'),
+      'base' => 'classified',
+      'description' => t('By default, contains a title, a body, and an administrator-defined expiration date'),
+      'has_title' => TRUE,
+      'title_label' => t('Ad Title'),
+      'locked' => TRUE,
+    ),
+  );
+
+  return $ret;
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function classified_views_api() {
+  $ret = array(
+    'api' => 3,
+    'path' => drupal_get_path('module', 'classified') . '/views',
+  );
+  return $ret;
+}
+
+function __74______________________________________________________________() {}
+
+/**
  * Implements hook_block('view', 'popular').
  *
  * @return array
@@ -922,19 +1184,6 @@ function classified_delete($node) {
 }
 
 /**
- * Implements hook_entity_info_alter().
- *
- * - add the Classified Ad view mode (classified_content_build_modes() on D6)
- */
-function classified_entity_info_alter(&$entity_info) {
-  $entity_info['node']['view modes']['classified'] = array(
-    // Was 'ad list' / t('Ad list') in Views 6.x.
-    'label' => t('Classified Ad'),
-    'custom settings' => TRUE,
-  );
-}
-
-/**
  * Implements hook_field_extra_fields().
  *
  * Allow repositioning of the expires box
@@ -961,24 +1210,6 @@ function classified_field_extra_fields() {
 }
 
 /**
- * Implements hook_field_formatter_info().
- *
- * - Classified Ads term reference formatter links to Classified Ads pages, not
- *   standard taxonomy pages.
- */
-function classified_field_formatter_info() {
-  $ret = array();
-  $ret['taxonomy_term_reference_classified_link'] = array(
-    'label' => t('Classified Ads link'),
-    'description' => t('A link to the Classified Ads per-category listing for ad categories, or to default taxonomy pages otherwise'),
-    'field types' => array('taxonomy_term_reference'),
-    'settings' => array('link_title' => FALSE),
-  );
-
-  return $ret;
-}
-
-/**
  * Implements hook_field_formatter_settings_form().
  *
  * - should Classified Ads formatter add a "title" attribute ?
@@ -1345,28 +1576,6 @@ function classified_node_access($node, $op, $account) {
 }
 
 /**
- * Implements hook_node_info().
- *
- * @todo XXX 2011-08-04 FGM: note in D6 version to check
- *   "match nodeapi $node->type for spam module to add spam reporting links"
- */
-function classified_node_info() {
-  $ret = array(
-    'classified' => array(
-      // Cannot call node_get_types() since it ends up calling this code.
-      'name' => t('Classified Ad'),
-      'base' => 'classified',
-      'description' => t('Contains a title, a body, and an administrator-defined expiration date'),
-      'has_title' => TRUE,
-      'title_label' => t('Ad Title'),
-      'locked' => TRUE,
-    ),
-  );
-
-  return $ret;
-}
-
-/**
  * Implements hook_node_presave().
  *
  * Auto-assign expiration date before saving, for both update and insert.
@@ -1506,30 +1715,8 @@ function classified_permission() {
  * - remaining: days to expiration
  * - remaining_ratio: the percentile of ad lifetime already expired
  */
-function classified_preprocess_classified_expires(&$variables) {
-  $node = $variables['node'];
-  $now = REQUEST_TIME;
-  $expires_raw = $node->expires;
-  $variables['expires_raw'] = $expires_raw;
-  $date_format = _classified_get('date-format');
-  $variables['expires'] = strftime($date_format, $expires_raw);
-  $variables['remaining'] = format_interval($expires_raw - $now, 1);
-
-  $remaining_ratio = ($expires_raw > $now)
-    ? round(100 * (($expires_raw - $now) / ($expires_raw - $variables['node']->created)))
-    : 0;
-  $variables['remaining_ratio'] = $remaining_ratio;
-  // Color-code ratios.
-  if ($remaining_ratio == 0) {
-    $class = 'classified-expires-expired';
-  }
-  elseif ($remaining_ratio < 20) {
-    $class = 'classified-expires-soon';
-  }
-  else {
-    $class = 'classified-expires-later';
-  }
-  $variables['remaining_class'] = $class;
+function classified_preprocess_classified_expires_rich(&$variables) {
+  ExpiresField::preprocessExpiresRich($variables);
 }
 
 /**
@@ -1635,9 +1822,14 @@ function classified_term_path($term) {
  */
 function classified_theme($existing, $type, $theme, $path) {
   $ret = array(
-    'classified_expires' => array(
-      'variables' => array('node' => NULL),
-      'template' => 'classified-expires',
+    'classified_expires_rich' => array(
+      'variables' => array(
+        'node' => NULL,
+        'expires_type' => 0,
+        'expires_ts' => NULL,
+        'notified_ts' => NULL,
+      ),
+      'template' => 'classified-expires-rich',
       'path' => $path . '/theme',
     ),
 
@@ -1739,17 +1931,6 @@ function classified_view($node, $view_mode) {
 }
 
 /**
- * Implements hook_views_api().
- */
-function classified_views_api() {
-  $ret = array(
-    'api' => 3,
-    'path' => drupal_get_path('module', 'classified') . '/views',
-  );
-  return $ret;
-}
-
-/**
  * Implements hook_url_outbound_alter().
  *
  * When reporting broken taxonomy links, we want stack [2], because:
diff --git a/help/branch74.html b/help/branch74.html
new file mode 100644
index 0000000..c96d7cf
--- /dev/null
+++ b/help/branch74.html
@@ -0,0 +1,89 @@
+<div class="classified-help">
+  <h4>Node types</h4>
+  <p>As per #195494, the goal is to support multiple node types.</p>
+  <ul>
+    <li>The included node type must remain for ease of use</li>
+    <li>Other existing node types must be supported</li>
+    <li>Since extra fields are per-node-type, this implies replacing the extra
+      fields by a proper field</li>
+    <li>Ad lists can therefore no longer be built by node type.</li>
+    <li>Take advantage of the change to replace custom lists by default Views</li>
+    <li>Enabled node types can either be defined by module settings, or (better ?)
+      automatically be identified by the presence of an instance of the 
+      expiration field</li>  
+    </ul>
+    
+  <h4>Extra fields to Field</h4>
+  <p>The module defines two form extra fields, <code>expires_fs</code> on forms
+    and <code>expires</code> for display</p>
+  <ul>
+    <li>Kill both extra fields</li>
+    <li>Add a new Field replacing both, this means:
+      <ul>
+        <li>Adding a new widget to replace the form logic for <code>expires_fs</code></li>
+        <li>Adding a new formatter to replace the node view logic for <code>expires</code></li>         
+        </ul>
+      </li>
+    </ul> 
+    
+    <h4>Custom lists to Views</h4>
+    <p>The module has inner knowledge of the specifics of the expiration extra
+      fields.</p>
+    <p>Lists are built either using custom built lists including it, or using 
+      the new custom view mode introduced in 7.3. With multiple node types, this 
+      is no longer doable so we replace these lists by taxonomy-filtered views.</p>
+      
+    <p>With the extra field gone, its Views field handler no longer works.</p>
+    
+    <ul>
+      <li>Replace the expiration field handler</li>
+      <li>Replace the home page list with a view. Keep the various counters.</li>
+      <li>Replace the per-term list with a view. Keep the various counters, 
+        sub-category links, and breadcrumbs generation. This is likely to 
+        involve attached views and/or new specific handlers.</li>
+      <li>Replace the per-user profile list with a view. Handle the empty case.</li>
+      <li>Replace the per-user rendered and raw token with two displays of a view.</li>
+      <li>Replace the per-user link token, possibly still by manual code.</li>
+      </ul>
+      
+  <h4>Custom logic</h4>
+  <p>Most of the module complexity lies in the expiration handling: automatic
+    expires generation and update. This needs to be adapted for the additional
+    complexity of a field over an extra field. Existing node form hooks and
+    handlers must be replaced by field hooks since we no longer know which forms
+    to intercept.</p>
+    
+  <h4>i18n</h4>
+  <p>A number of users have expressed frustration at the lack of i18n support
+    in the module. Switching the extra fields to a field and the custom lists
+    to Views will help with this. New code should be i18n-compatible, so that
+    later changes can bring full i18n compliance</p>
+    
+  <h4>Testing</h4>
+  <p>The code currently has 100% S0 test coverage. This should not go down, but
+    increase towards 100% C0.</p>
+    
+  <ul>
+    <li>Rewrite tests to match the new code structures</li>
+    <li>Add tests for each new function/method introduced</li>
+    </ul> 
+    
+  <h4>Drupal 8</h4>
+  <p>Any significant work should take into account the directions taken by D8.
+    The move to an extra field is in the right direction. Other significant
+    changes regard code placement outside the module file and the use of 
+    autoloaded namespaced classes, one per file.</p>
+    
+  <ul>
+    <li>Strip all non-hook code from the module file and move it to class
+      methods</li>
+    <li>Action code externalized in so doing should be considered for being made
+      usable at the admin level by exposing it as a core action, typically for
+      the user or node entities</li>
+    <li>Consider using <code>xautoload</code> or <code>class_autoloader</code>
+      to apply PSR-0, or declare classes the D7 way, manually in the info file.</li>
+    <li>Consider replacing the existing taxonomy_term_reference instance by an
+      entityreference instance</li>      
+    </ul> 
+  </div>
+  
\ No newline at end of file
diff --git a/help/classified.help.ini b/help/classified.help.ini
index 9748b04..f9ab31c 100644
--- a/help/classified.help.ini
+++ b/help/classified.help.ini
@@ -33,3 +33,5 @@ weight = 2
 title = "Classified Ads API"
 weight = 3
 
+[branch74]
+title = "7.4 branch battle plan"
\ No newline at end of file
diff --git a/lib/Drupal/classified/CategoryField.php b/lib/Drupal/classified/CategoryField.php
new file mode 100644
index 0000000..ccf9549
--- /dev/null
+++ b/lib/Drupal/classified/CategoryField.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Classified Category field class file.
+ *
+ * @copyright (c) 2013 Ouest Systemes Informatiques (OSInet)
+ *
+ * @license General Public License version 2 or later
+ *
+ * Original code not derived from ed_classified.
+ */
+
+namespace Drupal\classified;
+
+
+class CategoryField implements FieldInterface {
+
+  /**
+   * hook_field_formatter_info() delegated implementation.
+   */
+  public static function formatterInfo() {
+    $ret = array();
+    $ret['taxonomy_term_reference_classified_link'] = array(
+      'label' => t('Classified Ads link'),
+      'description' => t('A link to the Classified Ads per-category listing for ad categories, or to default taxonomy pages otherwise'),
+      'field types' => array('taxonomy_term_reference'),
+      'settings' => array('link_title' => FALSE),
+    );
+
+    return $ret;
+  }
+
+  /**
+   * hook_field_formatter_settings_form() delegated implementation.
+   */
+  public static function formatterSettingsForm($field, $instance, $view_mode, $form, &$form_state) {
+    $display = $instance['display'][$view_mode];
+    $settings = $display['settings'];
+
+    $ret = array();
+    $ret['link_title'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('With link title'),
+      '#default_value' => $settings['link_title'],
+    );
+    return $ret;
+  }
+
+  /**
+   * hook_field_formatter_settings_summmary() delegated implementation.
+   */
+  public static function formatterSettingsSummary($field, $instance, $view_mode) {
+    $display = $instance['display'][$view_mode];
+    $settings = $display['settings'];
+    $ret = empty($settings['link_title']) ? t('Without link title') : t('With link title');
+    return $ret;
+  }
+
+  /**
+   * An ad may appear in multiple categories. Link will to Classified Ads pages
+   * only for terms in the Classified Ads vocabulary, and normal taxonomy pages
+   * otherwise.
+   */
+  public static function formatterView($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+    if (empty($items)) {
+      return;
+    }
+
+    $ret = array();
+    $vid = _classified_get('vid');
+
+    $tids = array();
+    foreach ($items as $delta => $item) {
+      $tids[] = $item['tid'];
+    }
+    $terms = taxonomy_term_load_multiple($tids);
+    foreach ($items as $delta => $item) {
+      $tid = $item['tid'];
+      if ($display['settings']['link_title']) {
+        $title = ($terms[$tid]->vid == $vid)
+          ? t('Classified Ads in the @title category', array('@title' => $terms[$tid]->name))
+          : t('Content flagged with @title', array('@title' => $terms[$tid]->name));
+        $attributes = array('title' => $title);
+      }
+      else {
+        $attributes = array();
+      }
+
+      $ret[$delta] = array(
+        '#markup' => l($terms[$tid]->name, classified_term_path($terms[$tid]),
+          array('attributes' => $attributes)
+        ),
+      );
+    }
+
+    return $ret;
+  }
+}
diff --git a/lib/Drupal/classified/ExpiresField.php b/lib/Drupal/classified/ExpiresField.php
new file mode 100644
index 0000000..1ff08c4
--- /dev/null
+++ b/lib/Drupal/classified/ExpiresField.php
@@ -0,0 +1,260 @@
+<?php
+
+/**
+ * @file
+ * Expires field class file.
+ *
+ * @copyright (c) 2013 Ouest Systemes Informatiques (OSInet)
+ *
+ * @license General Public License version 2 or later
+ *
+ * Original code not derived from ed_classified.
+ */
+
+namespace Drupal\classified;
+
+/**
+ * This field stores both an expiration type and and a manual expiration date.
+ */
+class ExpiresField implements FieldInterface {
+  const TYPE = 'classified_expires';
+
+  public static function attachPresave($entity_type, $entity) {
+dsm(get_defined_vars(), __METHOD__);
+  }
+
+  public static function attachSubmit($entity_type, $entity, $form, &$form_state) {
+dsm(get_defined_vars(), __METHOD__);
+  }
+
+  public static function attachUpdate($entity_type, $entity) {
+dsm(get_defined_vars(), __METHOD__);
+  }
+
+  public static function attachValidate($entity_type, $entity, &$errors) {
+dsm(get_defined_vars(), __METHOD__);
+  }
+
+  /**
+   * hook_field_create_field() delegated implementation.
+   *
+   * Force field configuration to be limited to node entities: hackish
+   * workaround for missing http://drupal.org/node/680910
+   */
+  public static function createField($field) {
+    $data = db_query('SELECT data FROM {field_config} fc WHERE fc.id = :fid', array(
+      ':fid' => $field['id'],
+    ))->fetchField();
+    // No need to check the result: this hook is fired right after field
+    // creation and saving to storage, so the data is always present.
+    $data = unserialize($data);
+    $data['entity_types'] = array('node');
+    $data = serialize($data);
+    db_update('field_config')
+      ->condition('id', $field['id'])
+      ->fields(array('data' => $data))
+      ->execute();
+
+    // There was another call right before invoking this hook, but other
+    // implementations may intervene, since it is called by module_invoke_all().
+    field_cache_clear();
+  }
+
+  /**
+   * hook_field_formatter_info() delegated implementation.
+   */
+  public static function formatterInfo() {
+    $ret = array();
+    $ret[self::TYPE] = array(
+      'label' => t('Classified Ad expiration'),
+      'description' => t('The scheduled expiration of an ad'),
+      'field types' => array('classified_expires'),
+      'settings' => array('format' => 'rich'),
+    );
+
+    return $ret;
+  }
+
+  /**
+   * hook_field_formatter_view() delegated implementation.
+   */
+  public static function formatterView($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+    if (empty($items)) {
+      return;
+    }
+
+    // There can not be more than 1 item.
+    // @see classified_field_update_forbid()
+    $item = $items[0];
+
+    $theme = "classified_expires_{$display['settings']['format']}";
+    $ret = array(
+      '#theme' => $theme,
+      // The entity type has been locked to "node".
+      '#node' => $entity,
+      '#expires_type' => $item['type'],
+      '#expires_ts' => $item['timestamp'],
+      '#notified_ts' => $item['notify'],
+    );
+
+    return $ret;
+  }
+
+  /**
+   * hook_field_info() delegated implementation.
+   */
+  public static function info() {
+    $ret = array();
+    $ret[self::TYPE] = array(
+      'label' => t('Classified: Expires'),
+      'description' => t('Store the encoded expiration date and type for a Classified Ad.'),
+      'default_widget' => 'classified_expires',
+      'default_formatter' => 'classified_expires',
+      'entity_types' => array('node'),
+    );
+
+    return $ret;
+  }
+
+  /**
+   * hook_field_is_empty() delegated implementation.
+   */
+  public static function isEmpty($item, $field) {
+    $ret = empty($item['value']);
+    return $ret;
+  }
+
+  /**
+   * hook_field_load() delegated implementation.
+   */
+  public static function load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
+    // dsm(get_defined_vars(), __METHOD__);
+  }
+
+  /**
+   * hook_field_schema() delegated implementation.
+   */
+  public static function schema() {
+    $ret = array(
+      'columns' => array(
+        'type' => array(
+          'description' => "The ad expiration type.",
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'timestamp' => array(
+          'description' => 'The Unix timestamp when the carrying node will expire.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'notify' => array(
+          'description' => 'The Unix timestamp of the latest notification for the carrying node',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+      ),
+      'indexes' => array(
+        'expires' => array('timestamp', 'type'),
+      ),
+      'foreign keys' => array(),
+    );
+
+    return $ret;
+  }
+
+  public static function preprocessExpiresRich(&$variables) {
+    // Pushed variables.
+    $created = $variables['node']->created;
+    $expires_ts = $variables['expires_ts'];
+
+    // Pulled variables.
+    $now = REQUEST_TIME;
+    $date_format = _classified_get('date-format');
+
+    // Generated variables.
+    $variables['expires_date'] = strftime($date_format, $expires_ts);
+    $variables['remaining'] = format_interval($expires_ts - $now, 1);
+
+    $remaining_ratio = ($expires_ts > $now)
+      ? round(100 * (($expires_ts - $now) / ($expires_ts - $created)))
+      : 0;
+    $variables['remaining_ratio'] = $remaining_ratio;
+
+    // Color-code ratios.
+    if ($remaining_ratio == 0) {
+      $class = 'classified-expires-expired';
+    }
+    elseif ($remaining_ratio < 20) {
+      $class = 'classified-expires-soon';
+    }
+    else {
+      $class = 'classified-expires-later';
+    }
+    $variables['remaining_class'] = $class;
+  }
+
+  /**
+   * hook_field_update_field() delegated implementation.
+   */
+  public static function updateField($field, $prior_field, $has_data) {
+dsm(get_defined_vars(), __METHOD__);
+  }
+
+  /**
+   * hook_field_update_forbid() delegated implementation.
+   *
+   * - Only allow cardinality 1.
+   */
+  public static function updateForbid($field, $prior_field, $has_data) {
+    $cardinality = $field['cardinality'];
+    if ($cardinality != 1) {
+      throw new \FieldUpdateForbiddenException(t('Cardinality can only be one for this field.'));
+    }
+  }
+
+  public static function validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
+dsm(get_defined_vars(), __METHOD__);
+  }
+
+  /**
+   * hook_field_widget_form() delegated implementation.
+   *
+   * - On the field configure form, return the field default value form
+   * - On the entity edit form, return the field input form
+   */
+  public static function widgetForm(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
+    $ret = array();
+
+    $ret['expires'] = $element;
+
+    $ret['expires_type'] = array(
+      '#type' => 'textfield',
+      '#title' => $element['#title'],
+    );
+    dsm(get_defined_vars(), __METHOD__);
+    return $ret;
+  }
+
+  /**
+   * hook_field_widget_info() delegated implementation.
+   */
+  public static function widgetInfo() {
+    $ret = array();
+    $ret[self::TYPE] = array(
+      'label' => t('Classified: Expires'),
+      'field types' => array(self::TYPE),
+      'settings' => array(),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    );
+    return $ret;
+  }
+}
diff --git a/lib/Drupal/classified/FieldInterface.php b/lib/Drupal/classified/FieldInterface.php
new file mode 100644
index 0000000..a12acc0
--- /dev/null
+++ b/lib/Drupal/classified/FieldInterface.php
@@ -0,0 +1,14 @@
+<?php
+namespace Drupal\classified;
+
+interface FieldInterface {
+  /**
+   * hook_field_formatter_info() delegated implementation.
+   */
+  public static function formatterInfo();
+
+  /**
+   * hook_field_formatter_view() delegated implementation.
+   */
+  public static function formatterView($entity_type, $entity, $field, $instance, $langcode, $items, $display);
+}
diff --git a/theme/classified-expires.tpl.php b/theme/classified-expires-rich.tpl.php
similarity index 61%
rename from theme/classified-expires.tpl.php
rename to theme/classified-expires-rich.tpl.php
index ec1a471..d8f9969 100644
--- a/theme/classified-expires.tpl.php
+++ b/theme/classified-expires-rich.tpl.php
@@ -5,12 +5,17 @@
  *
  * Variables:
  *
- * - $expires
- *   The date of expiration, in default classified.module format
- * - $expires_raw
- *   The date of expiration, as a UNIX timestamp
+ * - $expires_type
+ *   The type of expiration method. Not used in the default template.
+ *   - 0: node
+ *   - 1: reset
+ *   - 2: force
+ * - $expires_date
+ *   The date of expiration, in default Classified Ads format.
+ * - $expires_ts
+ *   The date of expiration, as a UNIX timestamp.
  * - $remaining
- *   The time remaining until expiration, in days
+ *   The time remaining until expiration, in days.
  * - $remaining_ratio
  *   The percentile ratio of remaining time to expiration over ad lifetime since
  *   creation.
@@ -21,17 +26,17 @@
  *   - 'classified-expires-soon'
  *   - 'classified-expires-later'
  *
- * @copyright (c) 2010 Ouest Systemes Informatiques (OSInet)
+ * @copyright (c) 2010-2013 Ouest Systemes Informatiques (OSInet)
  *
  * @author Frederic G. MARAND <fgm@osinet.fr>
  *
  * @license General Public License version 2 or later
  *
- * Original code, not derived from the ed_classified module.
+ * Original code, not derived from ed_classified.
  */
 ?><div class="classified-expires <?php echo $remaining_class; ?>"><?php
   echo t('Time remaining: @ratio%<hr />@expires (@remaining)', array(
-    '@expires'   => $expires,
+    '@expires'   => $expires_date,
     '@remaining' => $remaining,
     '@ratio'     => $remaining_ratio,
   ))?></div>
