Index: title.info
===================================================================
RCS file: /cvs/drupal/contributions/modules/title/title.info,v
retrieving revision 1.2
diff -u -r1.2 title.info
--- title.info	11 Oct 2010 19:59:57 -0000	1.2
+++ title.info	12 Oct 2010 20:18:51 -0000
@@ -4,5 +4,7 @@
 package = Multilingual
 core = 7.x
 dependencies[] = locale
+dependencies[] = translation
 files[] = title.module
+files[] = title.title.inc
 files[] = tests/title.test
Index: title.install
===================================================================
RCS file: /cvs/drupal/contributions/modules/title/title.install,v
retrieving revision 1.1
diff -u -r1.1 title.install
--- title.install	11 Oct 2010 19:56:17 -0000	1.1
+++ title.install	12 Oct 2010 20:18:51 -0000
@@ -6,3 +6,30 @@
  * Installation functions for Title module.
  */
 
+/**
+ * Implements hook_field_schema().
+ */
+function title_field_schema() {
+  $columns = array(
+    'value' => array(
+      'description' => 'A field replacing node title.',
+      'type' => 'varchar',
+      'length' => 255,
+      'default' => '',
+      'not null' => TRUE,
+    ),
+  );
+
+  return array(
+    'columns' => $columns,
+    'indexes' => array('title' => array('value')),
+  );
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function title_uninstall() {
+  variable_del('translation_title_entity_types_controller');
+  db_delete('{variable}')->condition('name', 'title_field_enabled_%', 'LIKE')->execute();
+}
Index: title.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/title/title.module,v
retrieving revision 1.19
diff -u -r1.19 title.module
--- title.module	11 Oct 2010 19:56:17 -0000	1.19
+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
  * Translatable entity title/label functionality.
  */
 
+/**
+ * Implements hook_field_info().
+ */
+function title_field_info() {
+  return array(
+    'title_field' => array(
+      'label' => t('Title'),
+      'description' => t('A field replacing node title.'),
+      'default_widget' => 'text_textfield',
+      'default_formatter' => 'text_default',
+      'no_ui' => TRUE,
+    ),
+  );
+}
+
+/**
+ * Implements hook_field_is_empty().
+ */
+function title_field_is_empty($item, $field) {
+  return empty($item['value']);
+}
+
+/**
+ * Implements hook_hook_info().
+ */
+function title_hook_info() {
+  $hooks['title_translate'] = array(
+    'group' => 'title',
+  );
+  return $hooks;
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ *
+ * Overwrite the common EntityController of the entity type with the one
+ * from this module.
+ */
+function title_entity_info_alter(&$entity_info) {
+  // This fetches the settings from the translation module
+  $entity_types = array_filter(variable_get('translation_entity_types', array()));
+
+  $original_controllers = array();
+  foreach ($entity_types as $entity_type) {
+    if (isset($entity_info[$entity_type])) {
+      $original_controllers[$entity_type] = $entity_info[$entity_type]['controller class'];
+      $entity_info[$entity_type]['controller class'] = 'TitleEntityController';
+    }
+  }
+
+  // Store default controllers
+  variable_set('translation_title_entity_types_controller', $original_controllers);
+}
+
+/**
+ * Implements hook_form_alter().
+ *
+ * Add a submit handle to node forms
+ */
+function title_form_alter(&$form, &$form_state, $form_id) {
+  $target_form = '_node_form';
+  if (substr($form_id, strlen($target_form) * -1) == $target_form) {
+    $bundle = str_replace($target_form, '', $form_id);
+    if (title_field_enabled($bundle)) {
+      array_unshift($form['#submit'], 'title_node_form_submit');
+    }
+  }
+}
+
+/**
+ * Callback for node forms
+ */
+function title_node_form_submit($form, &$form_state) {
+  if (title_field_enabled($form_state['node']->type)) {
+    // Choose the appropriate language - on insert the original language in
+    // translation is not set!
+    $langcode = (!empty($form_state['node']->translations->original)) ? $form_state['node']->translations->original : $form_state['node']->language;
+
+    // If the original language is changed, change the entity title value too
+    if (isset($form_state['values']['title_field'][$langcode][0]['value'])) {
+      $form_state['values']['title'] = $form_state['values']['title_field'][$langcode][0]['value'];
+    }
+  }
+}
+
+/**
+ * Implements hook_field_presave().
+ *
+ * TODO Do we have to synces on $entity->title back to title field too?
+ */
+function title_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  if (title_field_enabled($entity->type)) {
+    // Choose the appropriate language - on insert the original language in
+    // translation is not set!
+    $langcode = (!empty($entity->translations->original)) ? $entity->translations->original: $entity->language;
+
+    // If the original language is changed, change the entity title value too
+    if (isset($entity->title_field[$langcode][0]['value'])) {
+      $entity->title = $entity->title_field[$langcode][0]['value'];
+    }
+  }
+}
+
+
+/**
+ * Implements hook_field_attach_form().
+ */
+function title_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
+  if (title_field_enabled($entity->type)) {
+    $form['title']['#access'] = FALSE;
+  }
+}
+
+/**
+ * Checks if all requirements are meet to enable title translation
+ * @param string $bundle
+ * @return boolean
+ */
+function title_field_enabled($bundle) {
+  return variable_get('title_field_enabled_' . $bundle, FALSE);
+}
+
+/**
+ * Implements hook_field_attach_create_bundle().
+ * Make sure that new bundles, with title translation enabled, have the
+ * title_field instance
+ */
+function title_field_attach_create_bundle($entity_type, $bundle) {
+  title_configure_bundle($entity_type, $bundle);
+}
+
+/**
+ * Implements hook_field_extra_fields_alter().
+ */
+function title_field_extra_fields_alter(&$info) {
+  // Remove node title if title field is enabled
+  foreach (node_type_get_types() as $bundle) {
+    if (isset($info['node'][$bundle->type]['form']['title']) && title_field_enabled($bundle->type)) {
+      unset($info['node'][$bundle->type]['form']['title']);
+    }
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Remove default title field and remove settings link from title_field
+ */
+function title_form_field_ui_field_overview_form_alter(&$form, &$form_state, $form_id) {
+  if (isset($form['fields']['title_field'])) {
+    $form['fields']['title_field']['type']['#markup'] = $form['fields']['title_field']['type']['#title'];
+    $form['fields']['title_field']['type']['#cell_attributes']['colspan'] = 2;
+
+    unset(
+      $form['fields']['title_field']['type']['#type'],
+      $form['fields']['title_field']['type']['#title'],
+      $form['fields']['title_field']['type']['#href'],
+      $form['fields']['title_field']['type']['#options'],
+      $form['fields']['title_field']['widget_type']
+    );
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function title_form_translation_edit_form_alter(&$form, &$form_state, $form_id) {
+  $form['#submit'][] = 'title_form_translation_edit_form_submit';
+  // TODO Why do we have to define it here? Could we change module translate?
+  if (isset($form['submit']['#submit'])) {
+    $form['submit']['#submit'][] = 'title_form_translation_edit_form_submit';
+  }
+}
+
+/**
+ * Function to trigger hook_title_translate().
+ */
+function title_form_translation_edit_form_submit($form, &$form_state) {
+  // Clone the entity to prevent unintentional changes...
+  $entity = clone $form['#entity'];
+
+  // TODO Is this a good idea?
+  $entity->language = $form['#language'];
+  $entity->title = $entity->title_field[$form['#language']][0]['value'];
+
+  module_invoke_all(
+    'title_translate',
+    $form['#entity_type'],
+    $entity,
+    $entity->title_field[$form['#language']][0]['value'],
+    $form['#language']
+  );
+}
+
+/**
+ * Implements hook_menu_alter().
+ *
+ * TODO Better idea needed - anyone?
+ */
+function title_menu_alter(&$items) {
+  // Create tabs for all possible entity types.
+  foreach (entity_get_info() as $entity_type => $info) {
+    if (translation_enabled($entity_type)) {
+      $change_item = $entity_type . '/%' . $entity_type . '/translate';
+      if (isset($items[$change_item])) {
+        $items[$change_item]['page callback'] = 'title_translation_overview_alter';
+      }
+    }
+  }
+}
+
+/**
+ * Change translation overview
+ *
+ * TODO Better idea needed - anyone?
+ */
+function title_translation_overview_alter($entity_type, $entity) {
+
+  $build = translation_overview($entity_type, $entity);
+
+  // Add translated title to the overview of applicable
+  if (title_field_enabled($entity->type) && property_exists($entity, 'title_field')) {
+
+    $handler = translation_get_handler($entity_type, $entity);
+    $path = $handler->getViewPath();
+
+    $i = 0;
+    foreach (language_list() as $langcode => $language) {
+      if (isset($entity->title_field[$langcode])) {
+        $label = $entity->title_field[$langcode][0]['value'];
+        $row_title = $path ? l($label, $path, array('language' => $language)) : $label;
+        $build['translation_overview']['#rows'][$i][2] = $row_title;
+      }
+      $i++;
+    }
+  }
+
+  return $build;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Provide settings into the node content type form to enable
+ * title field.
+ */
+function title_form_node_type_form_alter(&$form, &$form_state) {
+  // Checkbox to enabled / disabled title field
+  $form['submission']['title_field_enabled'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Enable title field'),
+        '#description' => t('If enabled the title is stored as a common instead an extra field.'),
+        '#default_value' => variable_get('title_field_enabled_' . $form['#node_type']->type, FALSE),
+  );
+  // Add handler to manage the field instance
+  $form['#submit'][] = 'title_form_node_type_form_submit';
+}
+
+/**
+ * Callback to add / remove title_field instance to a bundle
+ */
+function title_form_node_type_form_submit(&$form, &$form_state) {
+  title_configure_bundle('node', $form['#node_type']->type);
+}
+
+/**
+ * Iterates over all bundles an makes sure the title_field is set correctly
+ */
+function title_configure_all_bundles() {
+  foreach (entity_get_info() as $entity_type => $info) {
+    $bundles = field_info_bundles($entity_type);
+    foreach ($bundles as $bundle => $bundle_info) {
+      title_configure_bundle($entity_type, $bundle);
+    }
+  }
+}
+
+/**
+ * Enables disables the title_field on a bundle
+ * @param string $entity_type
+ * @param strings $bundle
+ */
+function title_configure_bundle($entity_type, $bundle) {
+
+  // Make sure theres such a field to create a instance of
+  $field = field_read_field('title_field');
+  if (empty($field)) {
+    $field = array(
+        'field_name' => 'title_field',
+        'type' => 'title_field',
+        'cardinality' => 1,
+        'translatable' => TRUE,
+        'locked' => TRUE,
+    );
+    $field = field_create_field($field);
+  }
+  $field_type_info = field_info_field_types('title_field');
+
+  $instance = field_info_instance($entity_type, 'title_field', $bundle);
+
+  $state = title_field_enabled($bundle);
+  switch (TRUE) {
+    // Enable translatable title
+    case $state && !$instance:
+
+      // TODO There must be a smarter way to do this
+      $instances = field_info_instances($entity_type, $bundle);
+      $weight = 0;
+      foreach (field_info_instances($entity_type, $bundle) as $existing_instance) {
+        if (isset($existing_instance['widget']['weight']) && $existing_instance['widget']['weight'] < $weight) {
+          $weight = $existing_instance['widget']['weight'];
+        }
+      }
+      $weight--;
+      $instance = array(
+        'label' => t('Title'),
+        'description' => $field_type_info['description'],
+        'required' => TRUE,
+        'locked' => TRUE,
+        'field_name' => 'title_field',
+        'entity_type' => $entity_type,
+        'bundle' => $bundle,
+        'widget' => array('weight' => $weight),
+      );
+      field_create_instance($instance);
+      break;
+
+      // Disable translatable title
+    case !$state && $instance:
+      field_delete_instance($instance);
+      break;
+  }
+}
+
+/**
+ * Wrapper arround EntityController of a entity type.
+ * Designed to be compatible with all other changes made to controller class
+ * by adding a wrapper instead simply overwrite the original controller.
+ *
+ * @ TODO Review desperately needed - this is some strange solution :)
+ *
+ * @link http://drupal.org/project/entity_api (perhaps we should be using this)
+ */
+class TitleEntityController implements DrupalEntityControllerInterface {
+
+  /**
+   * @var DrupalEntityController
+   */
+  protected $aggregatedController;
+
+  /**
+   * Constructor: sets basic variables.
+   * @param $entityType
+   *   The entity type for which the instance is created.
+   */
+  public function __construct($entity_type) {
+
+    $aggregated_controller = 'DrupalDefaultEntityController';
+    $original_controllers = variable_get('translation_title_entity_types_controller', array());
+    if (isset($original_controllers[$entity_type])) {
+      $aggregated_controller = $original_controllers[$entity_type];
+    }
+    $this->aggregatedController = new $aggregated_controller($entity_type);
+  }
+
+  /**
+   * Make sure the wrapper grants access to the wrapped methods
+   * @TODO add check if called method is public?
+   */
+  public function __call($name, array $arguments ) {
+    $reflect_method = new ReflectionMethod($this->aggregatedController, $name);
+    if (!$reflect_method->isPublic()) {
+      throw new Exception('Fatal error: Call to private method ' . get_class($this->aggregatedController) . '::' . $name . '()');
+    }
+    return call_user_func_array(array($this->aggregatedController, $name), $arguments);
+  }
+
+  /**
+   * Make sure the wrapper grants access to the wrapped properties
+   * @TODO add check if requested property is public?
+   */
+  public function __get($name) {
+    $reflect_property = new ReflectionProperty($this->aggregatedController, $name);
+    if (!$reflect_property->isPublic()) {
+      throw new Exception('Fatal error: Call to private property ' . get_class($this->aggregatedController) . '::$' . $name);
+    }
+    return $this->aggregatedController->$name;
+  }
+
+  /**
+   * Make sure the wrapper grants access to the wrapped properties
+   * @TODO add check if requested property is public?
+   */
+  public function __set($name, $value) {
+    $reflect_property = new ReflectionProperty($this->aggregatedController, $name);
+    if (!$reflect_property->isPublic()) {
+      throw new Exception('Fatal error: Call to private property ' . get_class($this->aggregatedController) . '::$' . $name);
+    }
+    $this->aggregatedController->$name = $value;
+  }
+
+  /**
+   * Resets the internal, static entity cache.
+   */
+  public function resetCache() {
+    $params = func_get_args();
+    return call_user_func_array(array($this->aggregatedController, 'resetCache'), $params);
+  }
+
+  /**
+   * Loads one or more entities.
+   *
+   * @TODO: Implement proper language fallback
+   *
+   * @param $ids
+   *   An array of entity IDs, or FALSE to load all entities.
+   * @param $conditions
+   *   An array of conditions in the form 'field' => $value.
+   *
+   * @return
+   *   An array of entity objects indexed by their ids.
+   */
+  public function load($ids = array(), $conditions = array()) {
+    global $language_content;
+
+    $params = func_get_args();
+    $entities = call_user_func_array(array($this->aggregatedController, 'load'), $params);
+
+    foreach ($entities as &$entity) {
+      if (property_exists($entity, 'title') && title_field_enabled($entity->type)) {
+        // Make sure the original title is synced into the title_field
+        if (!isset($entity->title_field[$entity->translations->original])) {
+          $entity->title_field[$entity->language][0]['value'] = $entity->title;
+        }
+
+        // Use original language a default
+        $entity->title = $entity->title_field[$entity->translations->original][0]['value'];
+        if (isset($entity->title_field[$language_content->language][0]['value'])) {
+          $entity->title = $entity->title_field[$language_content->language][0]['value'];
+        }
+      }
+    }
+    return $entities;
+  }
+}
