CVS edit link for runish

Dear Sir / Madam

## Details ##
Type: Drupal module for 6.x
Projectname: crud (already exists, discussion below)

## About "crud" ##
Crud is a module that autogenerates admin forms from the database schema provided in the .install files. Extra information needed is set from a hook_crud(), which keeps the .install files untainted.

Crud currently has:

* one-to-many / many-to-one relationships
* the following field types: 'int', 'varchar', 'text', 'bool', 'weight'
* overwriting of form settings for individual fields
* caching
* support for cascading deletion
* having empty fields by setting 'not null' => TRUE in the .install file

## Motivation ##
I hate writing crud code, and I have created crud solutions for Joomla, Prescriba Framework (our own) and now drupal. This is the first time though I've coded support for relationships.

The reason I want to share this now, is that I could really need some eyes on this (naming conventions, bugfinding, code), and some people to discuss with.

I work for a healthcare company called Prescriba (www.prescriba.com), I have developed crud for them, and can partly support it from work as well.

## Naming ##
I see that there is allready a module called crud but that is merely some database functions. I dont care about the name, so let me know what you think.

## Duplication / crud module & Drupal overall strategy ##
I know there is a lot that could be said here, especially in regards to CCK, the new db_api comming in drupal 7, but I really don't have any answers. What do you think?

## State of the code ##
Its working fine and we have no known bugs with it, but I am sure there is many edge cases, that will show up if more people begin using it. There is also room for a _lot_ of improvement with the code in general. Also it would be nice to support many-to-many relationship properly. I would call it Alpha quality.

## Todo ##
* settle on naming conventions
* document all features
* 100% follow drupals coding conventions. ATM this is clashing a bit with that of my company. Looking for a way to auto convert back and forth.

## How it works ##
See code in crud_example module.

## Download ##
Consists of two modules:
* crud - the crud module
* crud_example module - a test module. Crud forms is accessed at http://[URL]/admin/labels

Download here: http://rune.prescriba.com/public/crud.tar.gz

Comments

_rune’s picture

Status: Postponed (maintainer needs more info) » Needs review
StatusFileSize
new44.86 KB
avpaderno’s picture

Status: Needs review » Needs work
  1. See the Drupal coding standards to understand how a module code should be written.
  2. As there is already a module with the same name, and with the same purpose (but for a different Drupal version), you should maybe offer to the maintainer of that module to become co-maintainer.
  3. The module seems to implement functionalities that are already present in Drupal core code, or that could be easily implemented using Drupal core functions.
_rune’s picture

Hi KiamLaLuno

Thx for your reply. My comments to that:

1) I am familiar with the "coding standards" and my current style is not very far from it. Last I ran the code through the coder module, It gave only minor errors. Should it be accepted as Drupal project, I will change it to 100% compliance with Drupal coding style. There is several thousands lines of code, and it will take a _long_ time to make it pass the coder module with 0 errors.

2) To quote from the current crud project:

"This is a utility / support module that simplifies SELECT, INSERT, UPDATE and DELETE operations for tables with a simple primary key (either an auto_increment key or a key defined in the sequences table)"

My module automatically generates:
* A list page of all items in the table (including pagination and ordering)
* An edit form for a single item
* A deletion form for a single item (including support for cascading deletion)
* Support for one-to-many / many-to-one and one-to-one table relationships

Only based on the schema in the .install file and a bit of additional configuration. So I _really_ don't think the two modules have the same purpose. But again I have no problems changing the name.

3) To automatically create forms based on database schemas is not currently a part of Drupal and can not easily be implemented using Drupal core functions. At least not without writing 80+ lines of code for each table you want implemented. Using crud module, it takes about 5 lines of configuration data for a table with no relationships.

Did you read through the code / try to install the modules?

Thanks for your time!

_rune’s picture

Status: Needs work » Needs review
StatusFileSize
new83.95 KB

Added a screenshot of what the code looks like and the result you get.

_rune’s picture

Status: Needs review » Needs work
StatusFileSize
new241.18 KB

Sorry for spamming. Here is a pdf file of the list,edit and delete view.

_rune’s picture

Status: Needs work » Needs review
DougKress’s picture

As you have stated, your module does a lot more than create, read, update & delete operations. I would recommend changing the name to something with the word "form" in it -- db_form, auto_form, form_builder.

The goals of the existing crud module is to simplify database operations. I've updated the description for the existing crud module (http://drupal.org/project/crud) with an example of the main crud_save function.

_rune’s picture

Thx a lot for quick reply.

To me Crud operations has always have something to do with forms, but I can see that you can consider CRUD both at the database level and at the User Interface level.

Will recommit this project under another name.

_rune’s picture

Status: Needs review » Closed (fixed)
avpaderno’s picture

Status: Closed (fixed) » Needs work

I am not sure it is possible to open another CVS application when one is already opened.
If you want really to not continue with this application, you should tell us.

_rune’s picture

Ahh ok sure. Will refactor my code into a new module name, and upload it to this thread. If just I could figure out a good name :)

Edit: Am thinking about "Auto Admin"

Thx!

avpaderno’s picture

CRUD is always associated with database operations, not with forms.

  1. The code doesn't follow the Drupal coding standards, in particular one suggestion that should avoid problems with other modules, which is not exactly a minor error.
  2. Some functions use the back tick character, which is specific to MySQL.
  3. Some functions should be rewritten because the code that calls any Drupal query function is supposed to use placeholders; the code of your module doesn't consider that, and uses db_escape_string().
_rune’s picture

Thx for feedback!

1) No you are right. I've already fixed the naming of the functions in crud.inflector.inc and db.inc (now crud.db.inc). In my own setup I have a inflector and a db module, so just copied the files from there and forgot to change the function names. Also fixed all errors when running it through the coder module. These changes will appear in my next upload.

2) Was not aware that backticks are MySQL specific, thanks for pointing that out.

3) I see what you mean, will change it into using placeholders instead of doing the MySQL by hand.

Still thinking about that name... will upload a new and improved version when I think of one.

_rune’s picture

Status: Needs work » Needs review
StatusFileSize
new15.47 KB

So here is a new version called: Auto Admin, folder name: "autoadmin".

## Changes ##
* Removed backticks. Might put them back in when mysql or mysqli driver is in use.
* Fixed naming of functions / files.
* Verified code with hardest setting in coder module.

## Not changed: Placeholders ##
I looked into how Drupal does it, and it does something similar to what I am doing: http://api.drupal.org/api/function/_db_query_callback/6, so my solution should not be more unsafe, although less optimized. My main problem is that I really like having the autoadmin_db_where() function that returns a string like "foo='bar' AND foz='baz'" from an array, and it gets more complicated (but certainly doable) to return an array like array("foo='%s' AND foz='%s'", array('bar', 'baz')). But if you really want, I will change it!

All the best
Rune

avpaderno’s picture

Status: Needs review » Needs work

The difference is that _db_query_callback() is a low level function that is never directly called from a module. It's quite obvious that such function exists, otherwise the queries could not be executed.
Modules should use placeholders, rather than building an SQL query by concatenating strings; being contained in a module, your code doesn't make an exception.

EDIT: There are two exception to the use of placeholders: when using update_sql() (which is called by update functions, and which doesn't allow the use of placeholders), and when the string is a constant (which means a string not obtained by any function).

_rune’s picture

Status: Needs work » Needs review
StatusFileSize
new1.67 KB

Hi

Here is an upload of autoadmin.db.inc with placeholders instead of manually escaping the sql. If it is in accordance with drupal guidelines (?) I will continue with the other files, but that is going to take some more work.

Cheers

avpaderno’s picture

Status: Needs review » Needs work
/**
 * returns placeholder based on type of value
 * 
 * @param mixed $value
 * @return string / FALSE
 */
function autoadmin_db_get_placeholder($value) {
  $type = gettype($value);
  switch ($type) {
    case 'string':
      return "'%s'";
    case 'integer':
    case 'boolean':
      return '%d';
    case 'double':
      return '%f';
    default:
      return FALSE;
  }
}

/**
 * returns whether a db result has rows WITHOUT moving the pointer
 *
 * @todo it puzzles me that this does not exist in drupal.
 * This function makes it so that only mysql,mysql and pgsql drivers can be used with autoadmin module.
 * @global string $db_type type of db connection
 * @param DbObject $result
 * @return bool
 */
function autoadmin_db_result_has_rows($result) {
  if (empty($result)) return FALSE;
  global $db_type;
  switch ($db_type) {
    case 'mysqli':
      return (mysqli_num_rows($result) > 0);
    case 'mysql':
      return (mysql_num_rows($result) > 0);
    case 'pgsql':
      return (pg_num_rows($result) > 0);
    default:
      return autoadmin_add_error(t("Database type: $db_type not supported"));
  }
}

These functions do something already done by a Drupal function.

avpaderno’s picture

Issue tags: +Drupal 6.x, +Module review
_rune’s picture

Status: Needs work » Needs review

## autoadmin_db_get_placeholder ##
If you are referring to http://api.drupal.org/api/function/db_type_placeholder/6 this is based on the type of field in the schema, whereas mine is based on the type of the variable. I _should_ use the schema information more in my database functions, but I would like to do that in a later version.

## autoadmin_db_result_has_rows ##
The closest I know of is http://api.drupal.org/api/function/db_result/6, but that moves the pointer of database object. The only other alternative I could find was to first do a "COUNT(*)" mysql call, but that is not so efficient.

avpaderno’s picture

Status: Needs review » Needs work

The closest I know of is http://api.drupal.org/api/function/db_result/6, but that moves the pointer of database object. The only other alternative I could find was to first do a "COUNT(*)" mysql call, but that is not so efficient.

I was thinking of db_affected_rows(), which can be used after an INSERT, UPDATE, REPLACE or DELETE query; when the previous query is a SELECT, it's more probable that db_result(db_query("SELECT COUNT(*) FROM {table} WHERE // ...")) is faster than the code of your function.

_rune’s picture

Status: Needs work » Needs review
StatusFileSize
new15.37 KB

Yir, you were right about the placeholders, even though it was a bit of a chore to change, it feels a lot nicer! Thx.

EDIT: Also helped med catch a couple of places where I forgot to escape variables before adding them to the sql.
EDIT2: Meaning that now I don't need to, because db_query() does it for me.

## Changes ##
* all db_query() calls uses placeholders
* removed the autoadmin_db_result_has_rows() function
* minor cleanups

_rune’s picture

StatusFileSize
new15.32 KB

Here is a new version.

## Changes ##
* No one line if sentences
* Non-documentation comments now are capitalized sentences with punctuation
* added @ingroup for form and @file docblocks
* added @see for form docblocks
* named parameters now have space around " = "

_rune’s picture

StatusFileSize
new14.18 KB

Hi again

Have not heard from anybody on this for a while, of reasons which I do not now, but I got a new version ready:

## Changes ##
* Removed all my custom database functions, everything is using db_query(), pager_query(), db_fetch_array() and drupal_write_record() now.
* Revamped code for SQL query generation.
* SQL generated is a lot more human readable
* The code is a lot tighter and easier to understand.
* Schemas with errors are not added to menu.
* Less magic more Drupal.

EDIT: The information about what this module actually does(Create administration forms automatically) is a bit scattered in this thread. Let me know if you need a rewrite of it!

EDIT2: To the best of my knowledge this version meets the changes suggested by KiamLaLuno.

EDIT3: KiamLaLuno has handled this process until now, but if he is too busy, maybe I could convince someone else to take a look? Sorry for nagging! :)

_rune’s picture

StatusFileSize
new15.16 KB

New version:

## Changes ##
* Added functional tests.

avpaderno’s picture

Status: Needs review » Needs work
  1. The module namespace is not respected.
  2.     '#type' => 'textfield',
        '#title' => autoadmin_util_pretty_title($field['key']),
        '#description' => !empty($field['description']) ? t($field['description']) : '',
        '#default_value' => _autoadmin_edit_field_get_value($row, $field, $schema),
        '#size' => $length,
        '#maxlength' => $length,
        '#required' => TRUE,
        '#weight' => $weight,
      );
    

    The title used for the field is not translated, and the description will never be translated because the script used to extract the strings to translate works only when the argument passed to t() is a constant string (not even a concatenation of strings).

  3.   $add = empty($field['unsigned']) ? 0 : 1;
      switch ($field['size']) {
        case 'tiny':
          return (3 + $add);
        case 'small':
          return (5 + $add);
        case 'medium':
          return (8);
        case 'normal':
          return (10 + $add);
        case 'big':
          return (20);
        default:
          return (10 + $add);
      }
    

    The code is making assumptions about the size of the field.

avpaderno’s picture

  $res = db_query("SELECT %s, %s FROM {%s}",
    $pk, $field['relation']['foreign alias'], $field['relation']['foreign schema']
  );

I am not sure this would work; the string that replaces the placeholder %s is passed to db_escape_string(), which is not used to escape a field name.

As reported in the previous comment, a function which returns the form field definition array doesn't allow to use translatable strings; just for this fact, I think it will be difficult to persuade other developers to use your module.

_rune’s picture

Hi

Thx a lot for feedback! Will address the issues mentioned. I could use some clarification regarding #26.

This works

  $res = db_query("SELECT %s, %s FROM {%s}",
    $pk, $field['relation']['foreign alias'], $field['relation']['foreign schema']
  );

But I agree that it is undrupalish. Would you prefer

  $res = db_query('SELECT ' . $pk . ',' . $field['relation']['foreign alias'] . ' FROM ' . $field['relation']['foreign schema']));

or

  $res = db_query(sprintf("SELECT %s, %s FROM {%s}",
    $pk, $field['relation']['foreign alias'], $field['relation']['foreign schema']
  ));

?

Cheers

avpaderno’s picture

Of the two choices, it's preferable the first one. Still, the SQL query you are building could suffer of SQL injection (especially if the module using yours is accepting inpout from a user).

_rune’s picture

Yes. All these three are taken from the schema supplied by the developer in hook_autoadmin(). Will change all places of using placeholders for SELECT and FROM statements into string concatenation, while keeping SQL injection in mind.

_rune’s picture

StatusFileSize
new14.24 KB

New version

## 25 ##
1. Now it is, or I just dont see it.
2. Everything in the userinterface is translateable now.
3. The size of the field was MySQL specific (http://help.scibit.com/Mascon/masconMySQL_Field_Types.html). Now returns default drupal values for other database engines.

## 26 ##
Fixed

## Other changes ##
* Cleanups and refactorings.

_rune’s picture

Status: Needs work » Needs review
_rune’s picture

StatusFileSize
new14.35 KB

Fixed a bug (and added tests) regarding pagination. Please review this version instead.

avpaderno’s picture

Status: Needs review » Needs work
  1. require AUTOADMIN_FILEPATH . '/autoadmin.util.inc';
    require AUTOADMIN_FILEPATH . '/autoadmin.db.inc';
    require AUTOADMIN_FILEPATH . '/autoadmin.query.inc';
    require AUTOADMIN_FILEPATH . '/autoadmin.schema.inc';
    require AUTOADMIN_FILEPATH . '/autoadmin.theme.inc';
    require AUTOADMIN_FILEPATH . '/autoadmin.inflector.inc';
    

    The code should use module_load_include().

  2.     'title' => t($schema['title_plural']),
        'description' => t($schema['description']),
    

    Title and description will not be translated, as the argument passed to t() is not a literal string.

  3.   drupal_set_message(
          ($schema ? '[SCHEMA: ' . $schema['table'] . '] ' : '')
        . ($field ? '[FIELD: ' . $field['key'] . '] ' : '')
        . t($msg)
        , 'error');
    

    The message passed to drupal_set_message() should use placeholders, and not concatenated strings (which cause the script that extracts the strings to translate to report an error); in the case $schema, or $field are empty, the message is simply a concatenation of two empty strings (what should a user understand, from that?).

  4. I am still wondering why a developer would adopt this module, when it is not possible to translate the strings it uses.
_rune’s picture

Status: Needs work » Needs review
StatusFileSize
new16.36 KB
new63.77 KB

Hey, thx for feedback!

## 1 ##
Fixed, but seems that it must run a lot more code now to do the same job.

## 2 ##
Fixed, see ## 4 ##

## 3 ##
Fixed bug in autoadmin_add_error(), and added the unit tests testAddErrorOneArgument(), testAddErrorTwoArguments(), testAddErrorThreeArguments() to test that it works.

## 4 ##
Think I finally understand the full extent of the problem now. Before I did not consider the need to make export/extract of .pot files work. Here are two different ways to define autoadmin schemas:

1)

$schema['album'] = array(
    // Dont autogenerate names based on table and field name
    'autoname' => FALSE,
    // Dont translate title, title_plural and description for schema and fields
    'translate' => FALSE,
    'title' => t('Album'),
    'title_plural' => t('Albums'),
    // Must repeat description here, because its against the drupal way
    // to use the t() function in .install files.
    'description' => t('Recorded albums'),
    'path' => 'admin/labels/albums',
    'alias' => 'title',
    'fields' => array(
      'id' => array (
        'title' => t('Id'),
        'title_plural' => t('Ids'),
      ),
      'title' => array(
        'title' => t('Title'),
        'title_plural' => t('Titles'),
      ),
      'description' => array(
        'title' => t('Description'),
        'title_plural' => t('Descriptions'),
      ),
      'weight' => array(
        'title' => t('Weight'),
        'title_plural' => t('Weights'),
      ),
      'label_id' => array(
        'title' => t('Label'),
        'title_plural' => t('Labels'),
      ),
    ),
    'relations' => array(
       'id' => array(
        'type' => 'has_many',
        'foreign table' => 'link_artist_album',
        'foreign key' => 'album_id',
        'foreign alias' => 'album_id',
      ),
      'label_id' => array(
        'type' => 'has_one',
        'foreign table' => 'label',
        'foreign key' => 'id',
        'foreign alias' => 'name',
      ),
    ),
  );

Here all text in the schema are using the t() function and can thus be extracted to a pot file.

2)

$schema['album'] = array(
    // Autogenerate names based on table and field name.
    'autoname' => TRUE,
    // Translate title, title_plural and description for schema and fields.
    'translate' => TRUE,
    'path' => 'admin/labels/albums',
    'alias' => 'title',
    'relations' => array(
       'id' => array(
        'type' => 'has_many',
        'foreign table' => 'link_artist_album',
        'foreign key' => 'album_id',
        'foreign alias' => 'album_id',
      ),
      'label_id' => array(
        'type' => 'has_one',
        'foreign table' => 'label',
        'foreign key' => 'id',
        'foreign alias' => 'name',
      ),
    ),
  );

This is a lot easier to write but extract of .pot files does not work. I plan to make a pot extracter. To translate this the user must manually search for each phrase in the translation interface, or use the Translation Client module (See attached screenshot showing a danish translation).

## Whats else is new ##
* Added languages folders in both autoadmin and autoadmin_example with extracted pot files.
* Cleanups and refactorings.

Thanks a lot!
Rune

avpaderno’s picture

Status: Needs review » Needs work

This is a lot easier to write but extract of .pot files does not work. I plan to make a pot extracter. To translate this the user must manually search for each phrase in the translation interface, or use the Translation Client module.

The function implemented by the proposed module are thought to be used from another module, not by the final user of a Drupal-powered site.
A module developer creating a module depending on the proposed one would find that his module uses some not translatable strings; he could write the same code present in the proposed module (or equivalent functions), and he would have translatable strings (to say to a user of a module that he needs another module to translate a module strings when this is not required from other modules is not something that would make the user happy).

The solution to the problem is to avoid the module emits any error messages, which should be a task left to the calling module (which will be free to use the error message it wants, and to have them really translatable without to require the user to install a third-party module).

_rune’s picture

Status: Needs work » Needs review
StatusFileSize
new16.66 KB

Hi Again

1)
I'm sorry but I don't understand what you mean about the messages. All error messages are translatable and gives hints to the developer when he has made a mistake in the schema definition.

2)
Drupal 6 does not support extraction of non literal strings, so I've figured that instead of creating yet another .pot extractor I could just write the strings as php code to a dummy file. This is not pretty but is the only way to make translation works seamlessly with the .pot extractor tools. See autoadmin_dynamic_t() and autoadmin_schema_write_strings_as_code(). This only runs during flushing of cache, and makes everything work as it should.

New version attached.
Cheers
Rune

avpaderno’s picture

Status: Needs review » Needs work
  1.     $links[] = array(
          'title' => t('Edit') . ' ' . $foreign_schema['title_plural'],
          'href' => $foreign_schema['path'],
          'query' => $field_relation['foreign key'] . '=' . $primary_value,
    

    The string is a concatenation of strings, when it should use a placeholder.

  2.   drupal_set_message(
          ($schema ? '[' . t('SCHEMA') . ': ' . $schema['table'] . '] ' : '')
        . ($field ? '[' . t('FIELD') . ': ' . $field['key'] . '] ' : '')
        . $msg
        , 'error');
    

    Also here the string is a concatenation of strings; some values of $schema, and $field would cause the message to be an empty string (which is not very useful as message).

  3. There are other strings which are concatenation of translatable strings; in such cases, the code should use placeholders.
_rune’s picture

Status: Needs work » Needs review
StatusFileSize
new17.83 KB

Added language files for autoadmin. Please review this version instead.

EDIT: disregard, you replied with #37 while I wrote this.

_rune’s picture

Status: Needs work » Needs review

1)
Since $foreign_schema['title_plural'] already is a translated string, to use this...

$links[] = array(
      'title' => t('Edit !title_plural', array('!title_plural' => $foreign_schema['title_plural'])),
);

...would add an unnecessary translation burden. You could argue that in some languages it might be more correct with the reverse ordering of words, but I think it is a good trade off.

2)
Message is never empty because the variable $msg is never empty. It is concatenated to the optional [SCHEMA: foo] [FIELD: bar]. But there is always a message.

3)
I have double checked the 84 uses of t() and autoadmin_dynamic_t() and they are all a conscious choice. The places I am not using placeholders are places where it would add an unnecessary translation burden.

Thx for feedback!

avpaderno’s picture

Status: Needs review » Needs work

You could argue that in some languages it might be more correct with the reverse ordering of words

There are actually languages where the reversed order of words is not the more correct, but it is the only way to write a sentence.
Languages where the order of the words is OSV don't write first the verb.

_rune’s picture

Status: Needs review » Needs work

LOL...this is cool:)

I'm by no means a linguist, did not even know what OSV meant before two minutes ago. Found this at http://jurisdynamics.blogspot.com/2008/02/themes-and-rhemes-and-xsv-smil....

OVS and OSV languages are "fantastically rare...found "maybe eight or ten languages with OVS order in simple transitive clauses . . . and maybe four with OSV.

So the current version does not support "Yoda" mode, as he apparently speaks mostly OSV;)

But I still strongly feel that with the extra amount of translation strings this would give (maybe about 20 for a standard size schema), it is not worth it.

But you are the boss and if you insist I will add something like a 'advanced translation' => TRUE setting, that translates with placeholders. But that seems very much to me to be a case of YAGNI. I would like to release it like it is now, and first change it if it becomes a problem. There are several other features/enhancements that in my eyes would enhance the module more.

Thx!
R

_rune’s picture

avpaderno’s picture

As far as I can see, other modules support any kind of languages (whatever they are SVO, OSV, etc...). As there is a request for adding the Central Morocco Tamazight in l.d.o, nobody can know if somebody will ask to add the support for a OSV language (Drupal supports any language).

_rune’s picture

Status: Needs work » Needs review
StatusFileSize
new17.78 KB

added support for Central Morocco Tamazight, and "Yoda Mode" :)

avpaderno’s picture

Status: Needs review » Needs work

May you explain how autoadmin.dynamic_strings.inc should resolve the problem of the strings not being translated?

_rune’s picture

Status: Needs work » Needs review

Generating php code in autoadmin.dynamic_strings.inc solves the problem of strings not being extracted with .pot tools.

This is how I solved translation of dynamically generated strings, so it works with drupal core modules:

Please consider the following code:

$foo['bar'] = 'Some text.';
var_dump(t($foo['bar']));

This is translatable. The user can browse to "admin/build/translate/search", search for "Some text.", translate it and it works fine. However it generates two problems:

1) Errors shown on "admin/build/translate/export" page. This I solved with the autoadmin_dynamic_t() function that wont trigger these errors.

2) "Some text." is not extracted to the .pot file generated at "admin/build/translate/export". This I solved with generating dummy php code and writing it to autoadmin.dynamic_strings.inc. See the autoadmin_schema_write_strings_as_code() function.

avpaderno’s picture

Status: Needs review » Needs work
  1. Generating php code in autoadmin.dynamic_strings.inc solves the problem of strings not being extracted with .pot tools.

    The content of the file is the following:

    // Adding dynamic strings as php code, so they will be extracted correctly by various language tools.
    t('Artist');t('Artists');t('Shows artists');t('Id');t('Ids');t('Name');t('Names');t('Instrument');t('Instruments');t('Gender');t('Genders');t('Label');t('Labels');t('The available record labels');t('Is Independent');t('Is Independents');t('Artist on Album');t('Artist on Albums');t('Many to many relationship link between artists and albums');t('Album');t('Albums');
    

    Are those all the dynamic strings the module could get? Considering that the module is thought to be potentially used from many modules, how can that file contain all the possible strings used by the modules?

  2. The use of a function like autoadmin_dynamic_t() is just a trick that doesn't resolve the problem of the extraction script not being able to extract the strings to translate. If the extraction script doesn't extract a string, then the template file doesn't contain that string, and people translating the module will not be able to translate it (as it is not present in the translation template).
_rune’s picture

Status: Needs work » Needs review

1) + 2) autoadmin.dynamic_strings.inc is regenerated by the autoadmin_schema_write_strings_as_code() function every time the cache is cleared, so it contains _all_ the strings from _all_ modules implementing autoadmin.

avpaderno’s picture

Status: Needs review » Needs work

Modules don't normally have write access to the directory where they are installed; that is the reason modules write their data in the directory files set by the Drupal installation they run on.
If I am wrong, let me know where it is stated differently.

_rune’s picture

Status: Needs review » Needs work

About using dummy file

Hey found this here: http://api.drupal.org/api/function/t

In some cases, modules may include strings in code that can't use t() calls. For example, a module may use an external PHP application that produces strings that are loaded into variables in Drupal for output. In these cases, module authors may include a dummy file that passes the relevant strings through t(). This approach will allow the strings to be extracted.
...
Having passed strings through t() in a dummy function, it is then okay to pass variables through t().

About the module having write access to its own folder

I checked out where t() functions are extracted from, and they have to be in one of the following folders:

  • modules
  • sites/all/modules
  • sites/all/themes
  • themes

I have neither found nor searched of precedence for a module writing to its own folder, but it is the only option to get the translation to work! Please bear in mind that if the developer is not happy with this he can simply add:

$schema['album'] = array(
    // Dont translate title, title_plural and description for schema and fields
    'translate' => FALSE,
    // ...
);

And no writing to disk will occur!!!

I think I've exhausted all the options on this, and landed on the best and most transparent solution.
Cheers
Rune

_rune’s picture

Status: Needs work » Needs review
avpaderno’s picture

What reported about a dummy file is true for strings that are not dynamically generated. To make an example, a dummy file can be used in the case the module uses third-party code, and the module knows which strings are returned from that code (it could be, in example, a string for the week day).

What your code does is to:

  1. Ask to the modules implementing a particular hook the strings they use.
  2. Write the obtained strings to a file, which is probably not writable from the module.

Am I missing anything?

_rune’s picture

Status: Needs review » Needs work

No, you are completely right. All strings from all the modules are written to sites/all/modules/autoadmin/autoadmin.potx.inc. So one file needs to be writable!

Writing to autoadmin.potx.inc can be disabled with the 'translate' => FALSE setting, and you have to define your module as the example in (#34.4.1).

The processing and pluralization of database field names to strings makes the job much easier for the developer! If I have a database field called "company" it is a fair assumption that I want the label in the form to be called "Company", and want to use the pluralized string "companies" other places. If I can't write to autoadmin.potx.inc I cant supply this feature. If you have another solution to this catch-22 i'd love to hear it :)

Cheers
Rune

_rune’s picture

Status: Needs work » Needs review
_rune’s picture

I occurred to me that CCK must face the same problem with translating field names inputted by the user. So I decided to check out how CCK handles the problems and it doesnt really. CCK is floating with code like:

'#title' => check_plain(t($field['widget']['label'])),
array('%label' => t($group['label']))),
content_filter_xss(t($group['settings']['form']['description'])),
return t($field_types[$field['type']]['formatters'][$this->options['format']]['label']);
// ...
// ...
// ...

So this code is bound to trigger warnings when extracting pot files.

Drupal is not really build for doing this kind of thing, and at least some kind of workaround must be applied.

_rune’s picture

Status: Needs work » Needs review

Did also check out the Views module. Here is what I found:

$options[$type] = t($info->name);
$options[$type] = check_plain(t($info->name));
return check_plain(t($output));
return t($this->options['perm']);
$storage[$key] = t($value);
$storage[$option] = t($definition['default']);
$title = t(ucwords($type));
return $this->render_link(t(check_plain($value)), $values);
avpaderno’s picture

Status: Needs review » Needs work

To ask to the modules the strings to translate is the right way to proceed; as the module is doing that, why doesn't it ask for the translated strings? In that way the single module could call t() passing to the function the literal string to translate.
There is not any problem if you use an already translated string in a call to t(); the important is to use a placeholder for that string (something like t('Error: %error')).

_rune’s picture

Status: Needs work » Needs review

Asking the modules for all the strings to translate allready works! It is just a LOT more annoying to write.

Sorry for repeating, but the difference is shown in the two examples below. In the first one _all_ strings are written. In the second one, autoadmin will decide what the strings should be. This decisionmaking is based on the names of the database names and database fields. The result is exactly the same.

1)

function example_autoadmin() {
$schema['album'] = array(
    // Dont autogenerate names based on table and field name
    'autoname' => FALSE,
    // Dont translate title, title_plural and description for schema and fields
    'translate' => FALSE,
    'title' => t('Album'),
    'title_plural' => t('Albums'),
    // Must repeat description here, because its against the drupal way
    // to use the t() function in .install files.
    'description' => t('Recorded albums'),
    'path' => 'admin/labels/albums',
    'alias' => 'title',
    'fields' => array(
      'id' => array (
        'title' => t('Id'),
        'title_plural' => t('Ids'),
      ),
      'title' => array(
        'title' => t('Title'),
        'title_plural' => t('Titles'),
      ),
      'description' => array(
        'title' => t('Description'),
        'title_plural' => t('Descriptions'),
      ),
      'weight' => array(
        'title' => t('Weight'),
        'title_plural' => t('Weights'),
      ),
      'label_id' => array(
        'title' => t('Label'),
        'title_plural' => t('Labels'),
      ),
    ),
    'relations' => array(
       'id' => array(
        'type' => 'has_many',
        'foreign table' => 'link_artist_album',
        'foreign key' => 'album_id',
        'foreign alias' => 'album_id',
      ),
      'label_id' => array(
        'type' => 'has_one',
        'foreign table' => 'label',
        'foreign key' => 'id',
        'foreign alias' => 'name',
      ),
    ),
  );

2)

$schema['album'] = array(
    // Autogenerate names based on table and field name.
    'autoname' => TRUE,
    // Translate title, title_plural and description for schema and fields.
    'translate' => TRUE,
    'path' => 'admin/labels/albums',
    'alias' => 'title',
    'relations' => array(
       'id' => array(
        'type' => 'has_many',
        'foreign table' => 'link_artist_album',
        'foreign key' => 'album_id',
        'foreign alias' => 'album_id',
      ),
      'label_id' => array(
        'type' => 'has_one',
        'foreign table' => 'label',
        'foreign key' => 'id',
        'foreign alias' => 'name',
      ),
    ),
  );

The only drawback is that autoadmin needs to be able to write to one single file. I'm all for purity of code but I also like nice features and ease of use. In this case I think it is ok to write to the file.

NB: I have found several other modules already in Drupal that are writing to their own module directories. If you want I can give you the specifics.

Thx
Rune

avpaderno’s picture

Status: Needs review » Needs work

Asking the modules for all the strings to translate allready works!

As you reported in the previous commit, the code asks to third-party modules the strings to translate, and then it writes them in a file contained in the module directory. Don't you see anything strange in the workflow? The modules could return the translated strings already, without the need to write them in a file.
Then, consider this (which is the main reason the code doesn't work). Users who want to translate the strings in your module downloads the archive, and run the module that extracts the strings to translate; as they didn't run the module (and to create the translation template is not necessary to execute the module first), that file doesn't contain any extra string (except the ones you already added).
Consider also how localize.drupal.org works; it doesn't execute the module to grab all the strings to translate.
Therefore, the proposed way to translate the strings doesn't work.

_rune’s picture

Status: Needs work » Needs review
StatusFileSize
new13.26 KB

I agree it would not work on localize.drupal.org, because the strings are collected inside autoadmin. Removed all functionality that relates to dynamic string generation.

Cheers
Rune

avpaderno’s picture

Status: Needs review » Fixed

I still think that using hook_autoadmin() to get the translated strings from third-party modules was a good idea.

_rune’s picture

I still miss my cool dynamic string generation (might move it to a generator script), but its more drupalish this way and its garantied to work 100% with translation. So I concur.

Its been some ride, thx a lot for all your help and comments!!! Autoadmin wouldn't be as good without them! Sorry if I've been stubborn along the way!

Take care
Rune

Status: Fixed » Closed (fixed)
Issue tags: -Drupal 6.x, -Module review

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

avpaderno’s picture

Component: Miscellaneous » new project application
Assigned: Unassigned » avpaderno
Issue summary: View changes
Status: Closed (fixed) » Fixed
Issue tags: -Drupal 6.x

Status: Fixed » Closed (fixed)

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