I am thinking about writing a small module for a quite special case and I would like to get some hints to point me in the right direction. The case is that I have a list of 'codes' that I need to add to a node in this way:

<img src="http://example.com/<CODE>" width="1" height="1" alt="" />

The code is unique for every node, but not every node necessarily has a code. Additionally, the list of new available codes must be downloaded every once in a while.

The perfect world: The list of up to 20 new codes is downloaded every once in a while and added to the database through a web interface. In the node edit form, the user has the chance to activate an option widget to add one of the available and unused codes. The code is then inserted into the node on the theme layer. Any ideas, how that could be accomplished?

Ideas I've had:

  • Add a simple text field and copy/paste the code there from a manually maintained list of codes.
  • Somehow feed a database table with the codes and offer them as a selection in the node edit form. Only codes that haven't been used before can be selected.

Views integration would be appreciated, too. That's why I have been thinking of a solution somehow connected to CCK.

Any suggestions?

Comments

jason_gates’s picture

You can discuss high level project requirements here:
http://groups.drupal.org/contributed-module-ideas

yan’s picture

Ok, I managed to create a small module that basically does what I want it to do:

- It provides a form to enter new codes
- It allows to select a CCK select list field to be used for the codes
- It adds allowed values to that field to select an available code
- It disables the select list once a code has been selected
- It provides a formatter for the CCK field to display the code

What I couldn't do was to provide the CCK field automatically. So far, the user has to create a CCK select list field, then choose it in the module's settings and then assign the custom formatter to it. I would like to have the module provide a CCK field so that those steps aren't necessary. Any hints on that?

Here's my module:

metis.info

name = Metis
description = Include the METIS pixel for VG Wort counts
dependencies[] = content
dependencies[] = text
core = 6.x

metis.install

// $Id: metis.install,v 1.0 2010/11/13 04:59:06 yan Exp $

/**
 * @file
 * Schema definitions install/update/uninstall hooks.
 */


/**
  * Implementation of hook_install().
  */

function metis_install() {

  // Use schema API to create database table.
  drupal_install_schema('metis');
  drupal_load('module', 'content');
  content_notify('install', 'metis');

}

/**
  * Implementation of hook_uninstall().
  */

function metis_uninstall() {

  // Use schema API to delete database table.
  drupal_uninstall_schema('metis');
  drupal_load('module', 'content');
  content_notify('uninstall', 'metis');

  // Delete our module's variable from the variables table.
  variable_del('metis_cck_field');

}

/**
  * Implementation of hook_enable().
  *
  * Notify content module when this module is enabled.
  */

function metis_enable() {

  drupal_load('module', 'content');
  content_notify('enable', 'metis');

}

/**
  * Implementation of hook_disable().
  *
  * Notify content module when this module is disabled.
  */

function metis_disable() {

  drupal_load('module', 'content');
  content_notify('disable', 'metis');

}

/**
  * Implementation of hook_schema().
  */

function metis_schema() {

  $schema = array();
  $schema['metis'] = array(
    'description' => t('Stores codes to use with the METIS pixel.'),
    'fields' => array(
      'code' => array(
        'type' => 'varchar',
        'length' => 128,
        'not null' => TRUE,
        'default' => ''
      ),
    ),
  );

  return $schema;

}

metis.module

// $Id: metis.module,v 1.0 2010/11/13 14:49:21 yan Exp $

/**
 * @file
 * The metis module
 *
 * Allows you to add the VG Wort mtis pixel to nodes
 */


/**
  * Define menu entries
  */

function metis_menu() {

  $items = array();

  $items['admin/settings/metis'] = array(
    'title' => t('Metis'),
    'description' => t('Metis settings'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('metis_settings_form'),
    'access arguments' => array('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
  );

  $items['admin/settings/metis/add'] = array(
    'title' => t('Add metis codes'),
    'description' => t('Add metis codes'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('metis_codes_form'),
    'access arguments' => array('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
  );

  return $items;
}


/**
  * Implementation of hook_help().
  */

function metis_help($path, $arg) {

  switch ($path) {

    // Main module help
    case 'admin/settings/metis':
      return '<p>' . t('The Metis module allows you to automatically insert the metis pixel provided by <a href="http://www.vgwort.de/">VG Wort</a>.') . '</p><p>' . t('To use the module you need to') . '</p><ol><li>' . t('Add a CCK select list field to your node types and then select it on this page') . '</li><li>' . t('Set the display settings of that field to "metis" for full node view') . '</li></ol>';

  }
}


/**
  * Define the settings form
  */

function metis_settings_form() {

  // Get CCK fields
  $fields = content_fields();

  // Add empty option
  $options = array(NULL => 'None');

  foreach ($fields as $field) {

    // Show only fields with select list
    if ($field['widget']['type'] == 'optionwidgets_select') {
      $options[$field['field_name']] = $field['widget']['label'] . ' (' . $field['field_name'] . ')';
    }

  }

  // Define a textarea
  $form['metis_cck_field'] = array(
    '#type' => 'select',
    '#required' => FALSE,
    '#title' => t('Select a field'),
    '#description' => t('Please select an existing CCK field (must be select list)'),
    '#default_value' => variable_get('metis_cck_field', array()),
    '#options'  => $options,
  );

  return system_settings_form($form);

}


/**
  * Define the form for entering a code
  */

function metis_codes_form() {

  // Define a textarea
  $form['code'] = array(
    '#type' => 'textarea',
    '#title' => t('Metis code'),
    '#description' => t('Please enter Metis codes you want to save. <strong>One per line. Every code must be 32 letters long.</strong>'),
    '#cols' => 32,
    '#rows' => 20,
    '#required' => TRUE,
  );

  // Define a submit function.
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );

  return $form;

}


/**
  * Validate the input
  */

function metis_codes_form_validate($form, &$form_state) {

  // Get rid of white spaces
  $codes = trim($form_state['values']['code']);

  // Split by line
  $codes = explode("\n", $codes);

  // remove any extra \r characters left behind
  $codes = array_filter($codes, 'trim');

  foreach ($codes as $code) {

    if (drupal_strlen(trim($code)) != 32) form_set_error('metis_codes', t("%c is not 32 letters long", array('%c' => $code)));

  }

}


/**
  * Handle submission of the metis codes and saving
  * of the data to the database.
  */

function metis_codes_form_submit($form, $form_state) {

  // Get rid of white spaces
  $codes = trim($form_state['values']['code']);

  // Split by line
  $codes = explode("\n", $codes);

  // remove any extra \r characters left behind
  $codes = array_filter($codes, 'trim');

  // Implode values for the confirmation message
  $codes_print = implode('<br />', $codes);

  // Write codes to database
  foreach ($codes as $code) {
    db_query("INSERT INTO {metis} (code) VALUES ('%s')", str_replace("\r", '', $code));
  }

  // Set confirmation message
  drupal_set_message(t("%n codes have been saved", array('%n' => count($codes))) . ':<br />' . $codes_print);

}


/**
  * Implementation of hook_form_alter().
  * To disable the select list once a code has been selected
  * From http://drupal.org/node/357328
  */

function metis_form_alter(&$form, $form_state, $form_id) {

  // Get field
  $field = variable_get('metis_cck_field', NULL);

  if ($field) {

    if (isset($form['#node']->{$field}[0]['value']) && isset($form['type']) && isset($form['#node'])) {

      // Use this check to match node edit form for any content type.
      if ($form['type']['#value'] .'_node_form' == $form_id) {
        $form['#after_build'][] = '_metis_after_build';
      }

      // Use this check to match node edit form for a particular content type.
      // if ('mytype_node_form' == $form_id) {
      //   $form['#after_build'][] = '_metis_after_build';
      // }

    }
  }

  // Inject PHP allowed values into field
  // module_load_include('inc', 'metis', 'cck_inject_allowed');
  // require_once ./metis_cck_inject_allowed.inc;
  $form['#field_info'][$field]['allowed_values_php'] = '
    $field = variable_get(\'metis_cck_field\', NULL); // The machine readable name of the field
    $options = array();

    // Check if node already has a code set in this field on node edit
    if (arg(0) == \'node\' && is_numeric(arg(1)) && arg(2) == \'edit\'){

      $node = node_load(' . $form['nid']['#value'] . ');

      if ($node->{$field}[0][\'value\']) {

        // If code is set, use it and exit
        $options[$node->{$field}[0][\'value\']] = t(\'Embed Metis code\') . \' (\' . $node->{$field}[0][\'value\'] . \')\';

        return $options;

      }

    }

    if (!$node->{$field}[0][\'value\']) {

      $db_field = content_fields($field);
      $db_info = content_database_info($db_field);

      if ($db_info[\'columns\'][\'value\'][\'column\']) {

        $query = "SELECT metis.code
                  FROM metis
                  LEFT JOIN %s ON ( %s.%s = metis.code )
                  WHERE %s.%s IS NULL
                  LIMIT 1";


        $row = db_fetch_object(db_query($query, $db_info[\'table\'], $db_info[\'table\'], $db_info[\'columns\'][\'value\'][\'column\'], $db_info[\'table\'], $db_info[\'columns\'][\'value\'][\'column\']));
        $options[$row->code] = t(\'Embed Metis code\') . \' (\' . $row->code . \')\';

      }
    }

    return $options;
  ';

}


/**
  * Custom after_build callback handler.
  * To disable the select list once a code has been selected
  */

function _metis_after_build($form, &$form_state) {

  $field = variable_get('metis_cck_field', NULL);

  if ($field) {

    // Use this one if the field is placed on top of the form.
    _metis_fix_disabled($form[$field]);

    // Use this one if the field is placed inside a fieldgroup.
    // _metis_fix_disabled($form['group_mygroup'][$form[$field]]);

  }

  return $form;

}


/**
  * Recursively set the disabled attribute of a CCK field
  * and all its dependent FAPI elements.
  */

function _metis_fix_disabled(&$elements) {

  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {

      // Recurse through all children elements.
      _metis_fix_disabled($elements[$key]);
    }
  }

  if (!isset($elements['#attributes'])) {
    $elements['#attributes'] = array();
  }
  $elements['#attributes']['disabled'] = 'disabled';
}


/**
  * Implementation of hook_field_formatter_info().
  *
  * Here we define an array with the options we will provide in display fields page
  * The array keys will be used later in hook_theme and theme_
  */

function metis_field_formatter_info() {

  $formatters = array(
    'metis' => array(
      'label' => t('Include as a metis pixel'),
      'field types' => array('text'),
      'description' => t('Embeds the fields value as metis code.'),
    ),
  );

  return $formatters;

}


/**
  * Implementation of hook_theme().
  *
  * We declare our theme functions according to the array keys in  hook_field_formatter_info
  */

function metis_theme() {

  $theme = array(
    'metis_formatter_metis' => array(
      'arguments' => array('element' => NULL),
    ),
  );

  return $theme;

}


/**
  * Theming functions for our formatters
  *
  * And here we do our magic. You can use dsm($element) to see what you have to play with (requires devel module).
  */

function theme_metis_formatter_metis($element) {

  $output = '<img src="http://vg01.met.vgwort.de/na/' . $element['#item']['value'] . '" width="1" height="1" alt="">';

  return $output;

}
rsmr’s picture

I'd love to use your module, but it fails to offer the codes when creating or editing a page. The only option then is "- None -". I have installed the module, successfully added codes (confirmed by looking in the raw SQL database), set up a CCK field, selected it in the metis admin page, and set the field display mode to the metis pixel...

Any clues? Are you still working on the module? Maybe there is an updated version?

rsmr’s picture

just to add to this: If I change the field type to simple text field and enter the code manually, it works: the pixel gets included all right, and the field is grayed out when I edit again (since one metis pixel should only be used once). It would be marvellous, however, if the other functionality would also work: that I can store a list of METIS codes from which one code gets chosen whenever a page is first created. This code should then be fixed on this node and removed from the list of codes (so that it won't be chosen a second time)... It seems like metis_form_alter should do the trick, but doesn't...

rsmr’s picture

once more: it only works, however, if the cck field is still set as the metis field in the admin pages (which is only possible while it is still a select box). Workflow: create metis field as select box, pick it in the metis admin pages, then change field type to simple text to be able to manually enter codes...

yan’s picture

Hi rsmr,

please excuse that it took me so long. I haven't been working on the module lately but at least I have a version that works more or less on on clean install. I will send it to you since I can't attach it here.