The basic idea is exploiting the Field API to define a Title field and attach it to nodes.

On node load (see hook_entity_load) we would replace $node->title value with $node->title_field[$langcode][0]['value'], where $langcode is $language_content->language. The title field will be hidden by default while viewing the node.

On the node form we must hide the title text field. Before saving we must put in $node->title<code> the value of <code>$node->title_field[$node->language][0]['value'].

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

das-peter’s picture

Status: Active » Needs review
FileSize
22.14 KB

Initial code.

Features:

  • Configuration to enable title translation per entity_type. (/admin/config/regional/translation)
    • If title translation is enabled for a content type, the translatable_title field is bound to the bundles of the entity_type
  • Modifies field-management of bundles (e.g. /admin/structure/types/manage/page/fields):
    • If translation is enabled the default node title row is hidden
    • Removes settings-link of the translatable_title field
  • Modifies edit-form of bundles (e.g. node/add/page):
    • If translation is enabled the input field for the default node title is hidden
    • If the title is changed in the original language, $entity->title is changed too
  • Modifies translation overview (e.g. /node/1/translate):
    • Displays the language specific title
  • Extends the actions when saving a translation:
    • Provides a hook to be able to react on title translations
    • If module pathauto is enabled, it is triggered to create a path_alias (seems to be automatically extended to trigger also the module globalredirect)
  • Changes entity-loading by wrapping the entity_type specific entity controller (e.g. NodeController, CommentController):
    • Sets the value of $entity->title according to the language defined by $language_content
    • Wrapper is transparent - if the entity_type defines some special methods/properties they're still reachable.

Needs testing & review.

das-peter’s picture

Added some stuff:

  • Create translatable_title field instance when a new bundle is created (via hook_field_attach_create_bundle)
  • Added support for migrate module. Default title is mapped transparently to the translatable_title
das-peter’s picture

Enhanced migrate integration - triggers now title translatable hook too.

das-peter’s picture

Version: » 7.x-1.x-dev
FileSize
25.44 KB

Fix for migrate integration

plach’s picture

Sorry for the dealy, but I have been pretty busy these days. I'll try to give this a look tomorrow.

das-peter’s picture

No problem, just found a nasty issue.
I totally forgot to check the bundle specific multilingual-settings.

We have to make a decision about what should happen if a entity type has title translation enabled - but the bundle has node-translation configured.
Right now, an instance of the translatable_title field is attached to each bundle of a entity type where title translation is enabled - ignoring the bundles multilingual settings.

Here are the two choices I see:

  1. Just create the instance if the bundle has content-translation enabled.
    This means that the instance has to be added / removed when the multilingual settings of a bundle are changed.
    Here I'm concerned about the data consistency - already translated titles will be lost if the bundle configuration is changed.
  2. Keep adding the instance per entity type - but change the title behaviour only if the bundles has content translation enabled.
    This means some bundles will have a unused invisible field attached.
    Here I'm concerned about performance and it's ugly to have a hidden / unused field...

What's you suggestion? Any other idea?

plach’s picture

A quick note while I find the time for a proper review: the project has born as a helper for Translation but will live as an independent project: AAMOF many sites might need node titles behaving as fields besides their multilingual capabilities (e.g. think of HTML titles).

For this reason:

  1. I'd change the field name to title_field
  2. To answer your latest question: we just need a per-content-type option telling us if we have to attach the field to the bundle. Multilingual options should not influence this. For now we could adopt Field UI's behavior to preserve data consistency: once data has been created we don't allow this setting to be changed anymore.
das-peter’s picture

I've rewritten the whole thing - removed the per entity-type configuration.
Now there's only a check box to activate / deactivate the title-translations support.
All the rest is done based on the configuration of the translation module and the bundle settings.

After this rewrite a review is definitely needed - I had not enough sleep last night, hopefully the code quality is not battered ;)

plach’s picture

Well, to make the initial code easier to design and review I'd like having this patch split into smaller ones: first we should focus on the basic behavior (node title replacement), then we can work on integrating other modules. I'd like an issue for each different module so we can discuss every one separately.

That said, here is my first review (to be continued):

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+      'label' => t('Translatable title'),

Please, change this to "Title". As I said before translatability is only one of the benefits we get by having node title as a field. In my mind the replacement should be as transparent as possible to the users.

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+      'description' => t('This field stores a translated node title.'),

I'd change this to something like "A field replacing node title."

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+ * @TODO Do we need an index?

Yes, node title has it so I'd say we definitely need one.

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+      'description' => 'The language specific title of a entity',

See above.

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+/**
+ * Implements hook_entity_info_alter().
+ *
+ * Overwrite the common EntityController of the entity type with the one
+ * from this module.
+ */

This looks pretty complex to me, why not simply using http://api.drupal.org/api/function/hook_node_load/7?

Edit: we might want to check if this feasible by implementing http://api.drupal.org/api/function/hook_field_load/7.

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+/**
+ * Implements hook_field_attach_submit().
+ *
+ * @TODO How to make sure this module is the first whichs called?
+ */

Since we need to perform this on node save (no matter if the node comes from a form or an API call) I'd implement http://api.drupal.org/api/function/hook_field_presave/7. This way we are sure that no module implementing http://api.drupal.org/api/function/hook_field_attach_presave/7 can "beat" us. Anyway submit handlers can deal with node titles before, so to be sure we probably need to add a submit handler (as first one) to the node form to and update $node->title also there.

I suggest we should have an API function performing the replacement, something like title_node_sync($node), we can call in both places.

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+    $langcode = (strlen($entity->translations->original))? $entity->translations->original: $entity->language;

!empty($entity->translations->original) should be faster. Moreover there must be a white space vefore ? and :.

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+    // TODO Hide or remove?

$form['title']['#access'] = FALSE; should be the proper way.

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+function title_translation_enabled($bundle) {

title_field_enabled()

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+  return variable_get('translation_title_enabled', FALSE) && module_invoke('translation', 'supported_type', $bundle);

Why can't we just have variable_get("title_field_enabled_$bundle", FALSE)?

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+/**
+* Implements hook_form_FORM_ID_alter().
+*
+* Adds the title settings to the content translation configuration
+*/

Since title field has a more generic scope than just translation we need a configuration in the content type settings form.

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+ * Remove default title field and remove settings link from title_field

Isn't there a way to tell the Field UI we don't need a settings link? I don't really like this kind of form manipulation, but if it is the only way let's go with it.

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+      $form['fields']['title']

I think we can use http://api.drupal.org/api/function/hook_field_extra_fields_alter/7 to remove node title form field configuration.

+++ title.module	1 Oct 2010 19:56:46 -0000
@@ -1,108 +1,457 @@
+/**
+* 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']
+  );
+}

I don't get what this code is supposed to do. Would you please explain to me? I ain't sure understand it well, but two things:
1) Translation will depend on this, so if changes are needed over there to reach a better integration, we'll do them.
2) We don't need to care about node language in the translation form, since it can be changed only in the node edit form.

Powered by Dreditor.

das-peter’s picture

Node Title

Patch contains only the necessary changes - hook usage / migrate integration excluded.
Hook definition still included - see comment below.

Answers numbered according to code snippets above

  1. Label changed
  2. Description changed
  3. Index added
  4. Description changed
  5. I decided to use this more complex approach to increase the chances that the title module is the first one which deals with the title.
  6. Since submit and presave handler use different storages it's "difficult" to create a common function without adding "unnecessary" complexity.
    But at least I've added a submit handler besides the presave hook ;)
    Another question is, if the title_field_presave handler should check for changes made to $node->title and sync them back to the title field.
  7. Changed to !empty()
  8. Changed to $form['title']['#access'] = FALSE
  9. Function renamed to title_field_enabled()
  10. Since I've added the title field setting to the node type form we have now variable_get("title_field_enabled_$bundle", FALSE)
  11. Setting removed from translation management and added to node_type_form
  12. Unfortunately I've found nothing to handle that by configuration.
  13. Added hook_field_extra_fields_alter() - I simply unset the field right now, do you know a better way to handle this?
  14. The idea was to provide a hook, which fires when a title translation is saved - since e.g. pathauto won't notice a title translation.
    And because I need pathauto - and I guess a lot other users too - I've used the hook in title.title.inc (not included in this patch) to trigger pathauto.
    Of course the ideal solution would be that the modules using the $node->title would check if the title_field is enabled and use this instead.

According to http://drupal.org/node/902264 I've moved title_field_schema() into the ".install" file
Could you check the patch format, please? Is it better now?
What do you think, would we benefit from the test-bot in this early stage of the module?

sun’s picture

Hi there! I didn't really look at the patch, but it seems like this project needs a kick-start. :)

So I went ahead and committed the usual/common baseline of module files (also replacing the old title.module from 2004 that was still there). I hope that this heavily simplifies to roll this patch.

But unfortunately, this patch no longer applies now. :-| Can you re-roll once more?

+++ title.api.php	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,22 @@
\ No newline at end of file

Also, please make sure that this does not happen. You may want to use http://drupal.org/project/dreditor to review your own patches.

Powered by Dreditor.

plach’s picture

Sorry for being vacant, but I have been busy with killing the language system and core translation queues (there are 3-4 issues over there that would benefit from a passionate eye ;). I'll try to give this another look in a couple of days.

das-peter’s picture

Thanks guy's for feedback. I've re-rolled the patch.
And added ending lines - hope now it fits

sun’s picture

Status: Needs review » Needs work
diff -N title.info
--- /dev/null	1 Jan 1970 00:00:00 -0000

--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ title.info	1 Jan 1970 00:00:00 -0000

diff -N title.install
--- /dev/null	1 Jan 1970 00:00:00 -0000

--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ title.install	1 Jan 1970 00:00:00 -0000

mmmm -- did you cvs up? title.info, .install, .api.php, as well as a tests/title.test already exists in CVS HEAD now.

Also note that there are some tabs + trailing white-space in the files.

Powered by Dreditor.

das-peter’s picture

Status: Needs work » Needs review
FileSize
16.24 KB

Damn, don't know what went wrong before - this time I deleted not only the existing files but also the whole project to create everything from scratch.

das-peter’s picture

Just found an small issue in TitleEntityControllery. I was not carefully enough while copy paste some stuff.

plach’s picture

First of all: thanks for your work on this! I didn't test it yet, I'd like to have a "commitable" patch first, but here is my (hopefully) last code review:

+++ title.install	12 Oct 2010 20:18:51 -0000
@@ -6,3 +6,30 @@
+  db_delete('{variable}')->condition('name', 'title_field_enabled_%', 'LIKE')->execute();

No need for curly brackets in db_delete.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+/**
+ * Implements hook_hook_info().
+ */
+function title_hook_info() {
+  $hooks['title_translate'] = array(
+    'group' => 'title',
+  );
+  return $hooks;
+}

Can we leave Trigger integration for a later patch?

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+  // This fetches the settings from the translation module

Trailing dots are missing from many PHP docs/inline comments.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * Add a submit handle to node forms

"handler", missing trailing dot

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+  $target_form = '_node_form';
+  if (substr($form_id, strlen($target_form) * -1) == $target_form) {

We can implement title_form_node_form_alter() and remove this check.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+    $bundle = str_replace($target_form, '', $form_id);
+    if (title_field_enabled($bundle)) {

We can use $form['#node']->type here.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * Callback for node forms

Should be "Submit handler" instead of "Callback", missing trailing dot.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+  if (title_field_enabled($form_state['node']->type)) {

For consistency we could use $form['#node']->type also here.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+  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'];
+    }
+  }

As I was saying, we can make an API function out of this code, I'd say title_field_sync($node, $display = TRUE). We should have a parameter specifiying if we have to use the node language or the current display language (see http://api.drupal.org/api/function/field_language/7). So we don't need that check on $node->translations.

This way in the submit handler we can call title_field_sync((object) $form_state['values'], FALSE).

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * TODO Do we have to synces on $entity->title back to title field too?

No, for now I don't think we need to sync the title back.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+  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'];
+    }
+  }

Here we can call title_field_sync($entity, FALSE).

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * Implements hook_field_attach_form().

I'd move this code into title_form_node_form_alter() so it is not called for each entity form.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * Checks if all requirements are meet to enable title translation
+ * @param string $bundle
+ * @return boolean

PHPdocs messed up: for consistency "Checks" should be "Check". We are missing an empty line between the first line and the parameter descriptions, and we are missing also those :)

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * Implements hook_field_attach_create_bundle().

Missing empty line.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+function title_form_field_ui_field_overview_form_alter(&$form, &$form_state, $form_id) {

There is no $form_id param here.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+  // TODO Why do we have to define it here? Could we change module translate?

Sure, but I don't fully grok the title_translate hook yet, I'll have to test it after we finish the code review.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * TODO Better idea needed - anyone?

I really don't get why we need this.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * TODO Better idea needed - anyone?

We'll change translation to depend on title.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * Callback to add / remove title_field instance to a bundle

"Submit handler", missing trailing dot.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * Iterates over all bundles an makes sure the title_field is set correctly

Where do we need this?
Missing trailing dot.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+  // Make sure theres such a field to create a instance of

"there is", missing trailing dot

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+      // TODO There must be a smarter way to do this

node_field_extra_fields() defaults to -5. Perhaps we want to do the same to maintain consistent behavior.

+++ title.module	12 Oct 2010 20:18:52 -0000
@@ -6,3 +6,448 @@
+ * @ TODO Review desperately needed - this is some strange solution :)
+ *
+ * @link http://drupal.org/project/entity_api (perhaps we should be using this)

As I told you before I ain't sure this is the right approach: altering entity info is subject to the same order than implementing hook_field_attach_load() or hook_field_load() (better): other modules could overwrite our entity controller. IMO we should implement hook_field_load() and rely on a low module weight (say -0xFFFFFFFF) to act before any other one. The risk of actually not being the first exists anyway, but we'll have a far simpler code.

Sidenote: I took into consideration Entity API while coding the initial Translation patch, but basically it provides a CRUD API which we don't need atm. I'll keep an eye on it, however.

Powered by Dreditor.

das-peter’s picture

Thank you for the feedback. I'd like to see this RTBC asap too. But right now there are still some open points.
I've changed all the parts you mentioned - but for some of them I need some more feedback.

  • The hook_field_load() hook_field_attach_load() hooks don't work out for me. It seems like the hooks aren't triggered on each load.
    The only hook which works for me is hook_entity_load. Maybe I'd missed something but it looks like a caching issue.

  • For the sake of a small initial patch I've removed the changes for the translation overview too.
    Was just cosmetic anyway, now the overview shows again "n/a" instead the language specific title.

  •     title.module 12 Oct 2010 20:18:52 -0000
    @@ -6,3  6,448 @@
      * Implements hook_field_attach_form().
    

    I changed that too - but what's about the titles of other entity types? Is there a more general way instead using title_form_node_form_alter()?

  •     title.module 12 Oct 2010 20:18:52 -0000
    @@ -6,3  6,448 @@
     function title_form_field_ui_field_overview_form_alter(&$form, &$form_state, $form_id) {
    

    According to the API Doc of D7 there is a $form_id parameter.

  •     title.module 12 Oct 2010 20:18:52 -0000
    @@ -6,3  6,448 @@
       // TODO Why do we have to define it here? Could we change module translate?
    

    Removed like the other hook stuff. I moved all this stuff into a custom module because I need a pathauto integration right away for my project.

  •     title.module 12 Oct 2010 20:18:52 -0000
    @@ -6,3  6,448 @@
      * Iterates over all bundles an makes sure the title_field is set correctly
    

    Removed, was only used in my installation profile ;)

das-peter’s picture

Mistake in hook parameter definition fixed.

plach’s picture

Status: Needs review » Needs work
FileSize
6.32 KB

I tested the patch for the first time and it seems we are almost ready to go!

[...] what's about the titles of other entity types? Is there a more general way instead using title_form_node_form_alter()?

My goal for the title project is reaching a stable status ASAP with a basic set of features including strictly the ones needed to make Translation and the "node title as a field" behavior work. Afterwards I'd like to work on a general API allowing to replace any pseudo-field with a regular field for any entity. This would allow us to replace Taxonomy term names, for instance, or comment subjects or bodies.

Hopfully these are my last code-related remarks:

+++ title.info	20 Oct 2010 09:54:54 -0000
@@ -4,5 +4,8 @@
 package = Multilingual

Let's remove this line, I don't know which package we should be using, but Multilingual is misleading.

+++ title.info	20 Oct 2010 09:54:54 -0000
@@ -4,5 +4,8 @@
 dependencies[] = locale
+dependencies[] = translation

Please remove these dependencies, they are unneeded.

+++ title.info	20 Oct 2010 09:54:54 -0000
@@ -4,5 +4,8 @@
+files[] = title.title.inc
+files[] = title.migrate.inc

These files are not present atm.

+++ title.install	20 Oct 2010 09:54:54 -0000
@@ -6,3 +6,37 @@
+  db_query("UPDATE {system} SET weight = -0xFFFFFFF WHERE name = 'title'");

Sorry for making a wrong suggestion, but to have a 32 bit representation this should be -0x7FFFFFFF.

+++ title.module	20 Oct 2010 09:54:54 -0000
@@ -6,3 +6,242 @@
+function title_field_info() {

Probably we are missing some information, see the attached screenshot.

+++ title.module	20 Oct 2010 09:54:54 -0000
@@ -6,3 +6,242 @@
+ * Add a submit handler to node forms.

" and hide the standard title field."

+++ title.module	20 Oct 2010 09:54:54 -0000
@@ -6,3 +6,242 @@
+/**
+ * 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']
+    );
+  }
+}

After testing the patch I don't think we need this: as I was saying before this module will allow to have a regular field replacing node title; this means we will be able to choose the widget to use and set the text processing type (plain/filtered). Instead we want to implement http://api.drupal.org/api/function/hook_field_widget_info_alter/7 to tell the Field API which widget we support (for now just textfield).

+++ title.module	20 Oct 2010 09:54:54 -0000
@@ -6,3 +6,242 @@
+  if (property_exists($entity, 'title') && title_field_enabled($entity->type)) {

Why property_exists() and not isset(), which should be faster?

+++ title.module	20 Oct 2010 09:54:54 -0000
@@ -6,3 +6,242 @@
+    // Choose the appropriate language

Missing trailing dot.

+++ title.module	20 Oct 2010 09:54:54 -0000
@@ -6,3 +6,242 @@
+  // Checkbox to enabled / disabled title field.

"enable/disable". Please remove the whitespaces around the slash.

+++ title.module	20 Oct 2010 09:54:54 -0000
@@ -6,3 +6,242 @@
+    '#description' => t('If enabled the title is stored as a common instead an extra field.'),

I'd rephrase this: "If enabled node title is stored and behaves as a regular field."

+++ title.module	20 Oct 2010 09:54:54 -0000
@@ -6,3 +6,242 @@
+/**
+ * Implements hook_field_attach_load().
+ *
+ * TODO Doesn't work for me - seems not called on each load
+ */
+function title_field_attach_load($entity_type, $entities, $age, $options) {
+  foreach ($entities as &$entity) {
+    title_field_sync($entity_type, $entity);
+  }
+}
+
+/**
+ * Implements hook_field_load().
+ *
+ * TODO Doesn't work for me - seems not called on each load
+ */
+function title_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
+  foreach ($entities as &$entity) {
+    title_field_sync($entity_type, $entity);
+  }
+}
+

You are right, I did not take into account field cache. We can keep title_field_load(), since it will act before any hook_field_attach_load() implementation when field cache is empty. I think performing synchronization two times in this case should not be a great problem.

+++ title.module	20 Oct 2010 09:54:54 -0000
@@ -6,3 +6,242 @@
+ * TODO The only hook, I know, which works for me as
+ * replacement of the EntityController wrapper

Fine. Let's keep this as the primary way to perform synchronization. We need a comment here explaining what is the intended workflow, I'd say: "Since the result of field_attach_load() is cached, we perform title synchronization also here to ensure that we always have the correct value in $entity->title".

Powered by Dreditor.

sun’s picture

mmm, review of the review: LOL

db_query("UPDATE {system} SET weight = -0xFFFFFFF WHERE name = 'title'");

Ugh, please don't! :-) -100 should be more than sufficient. There are almost no modules that use a negative weight, and those that actually do, know that, too, and so they're using tiny negative weights only. Although I wonder why this is necessary in the first place (not having looked at the code again), but I hope that the code comments explain it.

"If enabled node title is stored and behaves as a regular field."

Again, not having read the code, I suspect this to be a description of a checkbox. Anyway, "if enabled" should be avoided. Instead, use clear and direct speech to express what effectively happens:

"Node titles will behave like any other field."

(which also reveals that this description does not clearly communicate what it tries to communicate -- what does it try to communicate? ;)

das-peter’s picture

Hi guys,

I've applied all the mentioned changes.

  • Changed install query to db_query("UPDATE {system} SET weight = -100 WHERE name = 'title'"); (as mentioned by sun) and added a comment.
  • I can't reproduce the issue plach attached as screenshot (thought the default values should be taken). Nevertheless I've added these settings to the field info.
  • Tested performance of property_exists() vs. isset(). Looks like the later is up to three times faster :0 . Frankly speaking, I don't know why I used property_exists(), but it's interesting, that a specialized function is so much slower.
  • Rephrased If enabled the title is stored as a common instead an extra field. to Store title as a common instead an extra field.. But I'm not sure if this makes clear what happens - @sun any suggestions? Idea is to express that the title will be handled like a common field and thus has the same features - e.g. multilingual support.
plach’s picture

I still get errors on node creation and view. I'm using the latest 7.x-1.x-dev and a plain installation with only Title enabled (no Locale, no Translation):

Notice: Undefined index: text_processing in _text_sanitize() (line 325 of E:\www\test\modules\field\modules\text\text.module).
Notice: Undefined index: text_processing in text_field_widget_form() (line 553 of E:\www\test\modules\field\modules\text\text.module).

Moreover we still have some minor string/coding standard issues:

+++ title.info	27 Oct 2010 06:21:31 -0000
@@ -1,8 +1,6 @@
+description = Replaces the node title by a normal field.

I'd say "Replaces with" is more correct.

+++ title.module	27 Oct 2010 06:21:31 -0000
@@ -6,3 +6,223 @@
+      	'text_processing' => '0',
+  			'max_length' => '255',

Leading tabs instead of spaces.

+++ title.module	27 Oct 2010 06:21:31 -0000
@@ -6,3 +6,223 @@
+ * @param string $entity_type
+ *  The name of the entity type.
+ * @param object $entity
+ *  The entity to work with.
+ * @param boolean $display
+ *  Specifies if the display- or the node-language is used.

There should be an indentation of 2 spaces between @param and the following line.

+++ title.module	27 Oct 2010 06:21:31 -0000
@@ -6,3 +6,223 @@
+  // Checkbox to enabled/disabled title field.

"enable/disable"

+++ title.module	27 Oct 2010 06:21:31 -0000
@@ -6,3 +6,223 @@
+ * Submit handler to add / remove title_field instance to a bundle.

Whitespaces around the slash.

+++ title.module	27 Oct 2010 06:21:31 -0000
@@ -6,3 +6,223 @@
+ * Enables / disables the title_field on a bundle.

As above.

+++ title.module	27 Oct 2010 06:21:31 -0000
@@ -6,3 +6,223 @@
+  foreach ($entities as &$entity) {
+    title_field_sync($entity_type, $entity);
+  }
...
+function title_entity_load($entities, $type) {

Let's just call title_entity_load() in title_field_load() .

+++ title.module	27 Oct 2010 06:21:31 -0000
@@ -6,3 +6,223 @@
+  foreach ($entities as &$entity) {
+    title_field_sync($type, $entity);
+  }

Please add a check on the entity type so we avoid the iteration on non-node entity lists.

Powered by Dreditor.

das-peter’s picture

Status: Needs work » Needs review
FileSize
8.29 KB

All mentioned issues should be fixed now

Notices were definitely my mistake - had inserted text_processing into the field- instead the instance-settings.
Test with a plain drupal installation was successful.

plach’s picture

Status: Needs review » Reviewed & tested by the community

Great work!

Let's give sun the chance to have a final look to this before committing it.

sun’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
8.58 KB

Hm.

1) Somehow, I didn't expect to see a limitation to nodes...? What about other entities like comments and taxonomy terms? We recently introduced generic entity label information to entity API: #629484: Add entity 'label' key info (e.g. title on node)

2) http://drupal.org/node/958162#comment-3696356 -- It would be good to group and order the functions in files by logical and chronological units. In this case: general module hooks, Field API integration, Entity API integration, Node/Form API integration, etc.

3) Why is the field type 'title_field' and not just 'title'? Only the field instance cannot use 'title' and also not 'field_title' in order to not override potentially existing entity properties or attached fields. In other words, the field type should be 'title', only the field instance name should be 'title_field'.

4) title_node_form_submit() clashes with auto-suggested and auto-invoked form submit handlers. An essential change in D7 is that Form API automatically invokes form handlers for the base form ID; i.e., while the form_id of a particular node form might be article_node_form, the base form ID is node_form, and thus, hook_article_node_form_submit() as well as hook_node_form_submit() is invoked in all modules.

5) Additionally, the code in title_node_form_submit() doesn't look correct to me. We need #entity_builders facility that the patch in #830704: Entity forms cannot be properly extended introduces.

Aside from that, good job! Attached patch performs some tweaks.

sun’s picture

FileSize
8.52 KB

Also replacing that switch construct, which was a bit weird.

das-peter’s picture

sun: thank you very very much for your review and the new patch.
It's awesome that you and plach take care about this module :)

I've no additions to your feedback - maybe after a more extensive test run on my installation...

And damn you got me: I've some kind of a switch() statement fetish - but only because I hate if/elseif/else constructs :P

sun’s picture

Before committing an initial patch, I'd highly recommend to explore a more generic implementation through the #629484: Add entity 'label' key info (e.g. title on node) facility (which has been committed already)

See http://api.drupal.org/api/drupal/includes--common.inc/function/entity_la...

That would be a bit more future-proof, and actually shouldn't be very hard to do.

Afterwards, we should remove the current checkbox from the node type configuration form, and, also based on the entity label information, integrate with Field UI's Manage Fields configuration page instead. Both the original/legacy title/label of an entity and also the title_field are locked and not configurable on this screen. Core has made sure that modules are able to alter that Field UI table, so it should be possible for us to inject a "Configure" link into the Operations for the title/label field row. The link can point to a custom form callback of Title module, but the resulting effect is that nothing in Title module is limited to nodes anymore. Any entity that is fieldable and has a label can get a title field.

Overall, I expect a decrease in SLOCs, even.

bojanz’s picture

The problem with entity_label() is that core itself doesn't use it, so if you remove all the syncing code and just declare a label callback, core would just display the plain old title.
entity_label is used only by contrib at the moment (efq_views, og, etc...).
This means that actually doing an entity_info_alter and adding a callback wouldn't give you anything that the current syncing code doesn't.

Sorry if this is already obvious.

plach’s picture

Status: Needs review » Needs work

@sun:

As bojanz is pointing out entity_label() won't work for core, we surely can add a label callback to support it, but this should be an additional measure not an alternative to synchronization.

I ain't sure we can fully generalize label handling and extending it to any entity, but if we go this way IMO we should have a basic API to replace any "custom" field with a "regular" field. Title should build on top of this API to handle the particular needs of node titles, which are fairly different from comment subjects, or term or user names, at least in core.

However, the last patch seems to work well, just a couple of issues: the title field should be hidden in the display settings by default, moreover title does not show up in node preview (full version). I found a few coding standard issues left:

+++ title.install	12 Nov 2010 19:04:27 -0000
@@ -6,3 +6,44 @@
+  // Make sure the title is properly handled before any other module
+  // access it.

Comment does not wrap at column 80.

+++ title.module	12 Nov 2010 19:32:59 -0000
@@ -3,6 +3,228 @@
+        'field_name' => 'title_field',
+        'type' => 'title',
+        'cardinality' => 1,
+        'translatable' => TRUE,
+        'locked' => TRUE,

Wrong indentation.

+++ title.module	12 Nov 2010 19:32:59 -0000
@@ -3,6 +3,228 @@
+ * synchronization also here to ensure that we always have the correct
+ * value in $entity->title.

Comment does not wrap at column 80.

Powered by Dreditor.

klonos’s picture

I started testing #27 just today and I am really exited to see how this is being implemented. It will eventually solve most of the language/path/translation/sync issues I'm having for some time now in all my D6 setups (list of related issues too long to post here). Here's a list of the issues I've spotted so far:

1. After one enables the 'Store title as a field' setting for a node, there is no way to revert that change (checkbox locked/grayed out). I don't know if this will ever be possible (?), but I am mentioning it anyways in hope to receive a definite answer by someone that understands the 'mechanism' behind this.

2. Once I enabled the 'Store title as a field' setting for the basic page content type and after creating a basic page node, it also displayed the title field (I believe that it was meant/intended to be hidden by default). Please see attached screenshot.

We might have hit a core bug here(?). My guess is that it only effects fields that have 'hidden' as the only available option for their format. Simply visiting the content type's 'Mange display' tab (../admin/structure/types/manage/page/display) and saving the node display places the title field in the hidden section where it should have been in the first place. See second screenie (before hitting the 'save' button) where the title field is listed along with the other fields visible fields, despite the fact that its format is set to 'hidden'.

plach’s picture

Assigned: Unassigned » plach

Working on Field UI/entity_label() integration to see what we can get.

@klonos:

About 1) see http://drupal.org/node/924968#comment-3516774.

We might have hit a core bug here(?).

I guess we had. It would be nice if you could investigate a little more about this and possibly open a core issue (please link it here if you do).

pcambra’s picture

suscribe

good_man’s picture

@plach: any patch to share? just want to ask so I won't duplicate the work.

plach’s picture

I haven't found the time for this yet, but I will in a few days. Don't lose hope ;)

good_man’s picture

No don't get me wrong, I said so because I might work on this today/tomorrow, so if you haven't worked on it maybe I can give it a try!

plach’s picture

I didn't write any line of code yet, if you want to try to work on this be sure to post an interdiff between your patch and #27.

plach’s picture

Status: Needs work » Needs review
FileSize
12.87 KB

Here is a partially working patch extending the Field UI to support any entity. As per #31 the patch introduces a field replacement API that can be used on any legacy pseudo-field (there is a docblock describing it in some more detail). entity_label() support is still TODO.

The main change wrt the previous patch is that the replacement field is provided through an hook for each legacy pseudo-field and can be managed through the Field UI as any other: when a lpf is replaced with a regular field, it disappears from the field overview form and gets back when the replacing field instance is removed.

If the direction is good I'll provide a complete patch implementing syncing and form massaging for any entity.

Please don't make a code style review, the patch is a huge mess from that POV and actually is needs work, just evaulte the direction taken.

plach’s picture

Here is the full patch implementing form massaging and synchronization for any entity. Comments and documentation should be ok too.

Please let me know what you think about this, because I'd really like to have a testable dev snapshot in a few days.

entity_label() support is still TODO because IMO we have bug in the API: the label callback function is called without specifying the entity type (just the entity), which makes impossible to achieve a generalized label handling. Probably we need to file a core issue to fix this.

plach’s picture

There were some trailing whitespaces all around.

fago’s picture

Status: Needs review » Needs work

very interesting.

* Updating the value on entity load is troublesome as soon as an entitycache would be plugged in. Also it does not apply the right language if the entity is supposed to be display in a different (language) context *if* the display makes use of the plain property value. Perhaps hook_entity_view() could be used instead load() + the value of the default language could be synced into the property before saving.
* The patch in #41 basically implements an API to replace plain entity properties with fields, so it could be used to make any entity property translatable / a field for any reason. So maybe title isn't the right module name any more?
* You could leverage the property info introduced by the entity API for supporting any non-field property, if you want for any data type with any property of the right data type (including field data) as source + the metadata wrappers could be utilized for setting/getting the property values.
However, the multi-lingual-API of the property info would probably need some improvements, i.e. mark properties as multi-lingual in the property info. Update: -> See #1031382: allow marking properties as translatable.

+function title_field_attach_load($entity_type, $entities, $age, $options) {
+  // @todo: Do we need to handle revisions here?
+  title_entity_load($entities, $entity_type);
+}

What's that? hook_entity_load() will be invoked anyway?
-> Marking as needs work.

Having entity_label() taking an optional langcode parameter sounds like a good thing to me.

sun’s picture

Status: Needs work » Needs review
rfay’s picture

I took #41 for test drive and it worked fine for me. Thanks!

The one thing that would have improved the UX would have been if the existing titles in existing nodes were copied into the new title field.

Looking forward to translatable entity titles.

BenK’s picture

Subscribing

good_man’s picture

title_field_sync($entity_type, &$entity, $legacy_field, $info['field']['field_name'], $display);

deprecated call by reference.

Scott J’s picture

So far, so good.

plach’s picture

Thanks to git people wishing to review/test some up-to-date code can have a look to the initial_work-924968 issue branch.

At the moment I'm working on some simple tests for the code recently added (mainly integration with the Entity API and support for the 'label callback' entity key).

I'm hoping to post a patch here soon for a final review.

klonos’s picture

...thanx for the update Francesco. This is great news!

plach’s picture

FileSize
33.38 KB

Ok, here is what I came up with after talking with sun, fago and rfay in Chicago.

The approach is not that much different from the one posted in #41:

  • the current patch introduces a dependecy from the Entity API module, mainly to generalize the entity language handling;
  • sun expressed some concerns about the fragility of the synchronization approach, I tried to address them by reversing part of the synchronization workflow: on load/view/submit field values are put into the legacy fields, instead on (pre)save field values are pulled from the legacy fields and synchronized back into fields; since title is supposed to act as the very first module on hook invocation, we cannot be sure that other presave hook implementations acting on the legacy fields do not get field values out of sync: for this reason field values are checked again after legacy field values are stored and get stored again if needed;
  • as previously discussed with fago, synchronization is now static cached as it can be performed on hook_field_attach_load, hook_entity_load, hook_entitycache_load (see #1089130: Allow modules to act on any entity type) and hook_entity_prepare_view (see #1089174: Prepare view hooks do not receive the language parameter);
  • we now have entity_label() integration for any entity replacing its label with a field; this is currently disabled due to #1096446: entity_label() is not passing along the $entity_type parameter.

If there is any other pending concern please let me know soon because I'd wish to release a development snapshot in a week, so more people can start testing this and we can start opening individual issues to polish things.

plach’s picture

I forgot: we have tests now!

plach’s picture

Status: Needs review » Needs work

The last submitted patch, initial_work-924968-50.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
FileSize
33.38 KB

Let's try again.

Status: Needs review » Needs work

The last submitted patch, initial_work-924968-54.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
FileSize
33.53 KB

Probably we need to commit an initial patch first. The attached one ships some small improvement on comments/phpdocs.

Status: Needs review » Needs work

The last submitted patch, initial_work-924968-55.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
plach’s picture

Status: Needs review » Fixed
FileSize
76.82 KB

Committed the attached patch to HEAD!

A development snapshot will be available in 12 hours.

klonos’s picture

Thanx Francesco, that's great!! ;)

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

  • Commit 38112e5 on 7.x-1.x, workbench by plach:
    Issue #924968 by plach, das-peter, sun, fago, klonos: Introduced field...
  • Commit 882c5f9 on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Added PHP docs
    
    
  • Commit 1efc48e on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Fixed entity forms to support nested subforms
    
    
  • Commit f922ccb on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Fixed resave needed if field values altered...
  • Commit 209a9b2 on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Added entity_label() support
    
    
  • Commit 08be0a6 on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Moved administration code into title.admin.inc
    
    
  • Commit b35d57e on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Introduced tests for field replacement workflow
    
    
  • Commit 40e5fdc on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Disabled entity_label() support until #1096446...
  • Commit 70dfc82 on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Fixed comments/PHP docs
    
    
  • Commit 0353793 on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Polished API as title_field_replacement_toggle...
  • Commit a65f448 on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Fixed field replacement checkbox broken
    
    
  • Commit 4810b6c on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Introduced tests for field replacement UI
    
    
  • Commit 109aa4e on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Improved comments/PHP docs
    
    
  • Commit fe83dd8 on 7.x-1.x, workbench by plach:
    Issue #924968 by plach: Updated the change log.