You are here

recipe.module in Recipe 7.2

Same filename and directory in other branches
  1. 8.2 recipe.module
  2. 5 recipe.module
  3. 6 recipe.module
  4. 7 recipe.module

Contains functions for Recipe node CRUD and display.

File

recipe.module
View source
<?php

/**
 * @file
 * Contains functions for Recipe node CRUD and display.
 */

// $block_delta constants.
define('RECIPE_BLOCK_SUMMARY', 1);

/**
 * Implements hook_help().
 */
function recipe_help($path, $arg) {
  switch ($path) {
    case 'admin/structure/recipe':
      $output = '<p>' . t('You can enable/disable bulk export formats on the <a href="@modules-page">modules page</a> in the recipes section.', array(
        '@modules-page' => url('admin/modules', array(
          'fragment' => 'edit-modules-recipe',
        )),
      )) . '</p>';
      return $output;
    case 'admin/structure/recipe/import_multi':
      $output = '<p>' . t('You can enable/disable bulk import formats on the <a href="@modules-page">modules page</a> in the recipes section.', array(
        '@modules-page' => url('admin/modules', array(
          'fragment' => 'edit-modules-recipe',
        )),
      )) . '</p>';
      return $output;
    case 'admin/config/content/recipe':
      return t('Configure the management and display of recipes.');
  }
}

/**
 * Implements hook_theme().
 */
function recipe_theme($existing, $type, $theme, $path) {
  return array(
    'recipe_landing_page' => array(
      'file' => 'recipe.landing.page.inc',
    ),
    // The ingredients sub-form on the recipe edit screen.
    'ingredients_form' => array(
      'render element' => 'form',
    ),
    'recipe_summary' => array(
      'function' => 'theme_recipe_summary',
      'variables' => array(
        'node' => NULL,
        'show_title' => TRUE,
      ),
    ),
    'recipe_duration' => array(
      'variables' => array(
        'duration' => 0,
      ),
    ),
    'recipe_ingredient' => array(
      'variables' => array(
        'name' => NULL,
        'quantity' => 0,
        'unit' => array(),
        'note' => '',
        'link' => 0,
      ),
    ),
  );
}

/**
 * Implements hook_permission().
 */
function recipe_permission() {
  return array(
    'export recipes' => array(
      'title' => t('Export recipes'),
      'description' => t('Export recipes in various formats.'),
    ),
    'import recipes' => array(
      'title' => t('Import recipes'),
      'description' => t('Import recipes in various formats.'),
    ),
  );
}

/**
 * Implements hook_load().
 */
function recipe_load($nodes) {
  $result = db_query('SELECT * FROM {recipe} WHERE nid IN (:nids)', array(
    ':nids' => array_keys($nodes),
  ));
  foreach ($result as $record) {
    $nodes[$record->nid]->recipe_yield = $record->yield;
    $nodes[$record->nid]->recipe_yield_unit = $record->yield_unit;
  }
}

/**
 * Implements hook_node_info().
 */
function recipe_node_info() {
  return array(
    'recipe' => array(
      'name' => t('Recipe'),
      'base' => 'recipe',
      'description' => t('Share your favorite recipes with your fellow cooks.'),
    ),
  );
}

/**
 * Implements hook_insert().
 *
 * Insert a new recipe into the database.
 */
function recipe_insert($node) {
  db_insert('recipe')
    ->fields(array(
    'nid' => $node->nid,
    'yield' => $node->recipe_yield,
    'yield_unit' => $node->recipe_yield_unit,
  ))
    ->execute();
}

/**
 * Implements hook_update().
 *
 * As an existing node is being updated in the database, we need to do our own
 * database updates.
 */
function recipe_update($node) {
  db_update('recipe')
    ->fields(array(
    'yield' => $node->recipe_yield,
    'yield_unit' => $node->recipe_yield_unit,
  ))
    ->condition('nid', $node->nid)
    ->execute();
}

/**
 * Implements hook_node_update().
 *
 * This enables the ingredient node feature. If there is an ingredient with the
 * same name as a node type ingredient, link them.
 */
function recipe_node_update($node) {
  if ($node->type == 'ingredient') {
    db_query("UPDATE {recipe_ingredient} SET link = :link WHERE LOWER(name) = :name", array(
      ':link' => $node->nid,
      ':name' => trim(strtolower($node->title)),
    ));
  }
}

/**
 * Implements hook_node_insert().
 *
 * This enables the ingredient node feature. If there is an ingredient with the
 * same name as a node type ingredient, link them.
 */
function recipe_node_insert($node) {
  if ($node->type == 'ingredient') {
    recipe_node_update($node);
  }
}
function recipe_load_ingredient($iid) {
  $result = db_query("SELECT id AS iid, name, link FROM {recipe_ingredient} WHERE id = :iid", array(
    ':iid' => $iid,
  ))
    ->fetchObject();
  return $result;
}

/**
 * Implements hook_delete().
 *
 * When a node is deleted, delete the recipe and recipe node ingredient links.
 * Leave the ingredients.
 */
function recipe_delete($node) {
  db_query("DELETE FROM {recipe} WHERE nid = :nid", array(
    ':nid' => $node->nid,
  ));
}

/**
 * Implements hook_form().
 */
function recipe_form($node, &$form_state) {

  // Title.
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title'),
    '#required' => TRUE,
    '#default_value' => !empty($node->title) ? $node->title : '',
    '#maxlength' => 255,
  );
  $form['recipe_yield'] = array(
    '#type' => 'textfield',
    '#title' => t('Yield'),
    '#default_value' => !empty($node->recipe_yield) ? $node->recipe_yield : '',
    '#size' => 4,
    '#maxlength' => 4,
    '#description' => t('The number of servings the recipe will make (whole number integer, ie 5 or 6).'),
    '#required' => TRUE,
  );
  $form['recipe_yield_unit'] = array(
    '#type' => 'textfield',
    '#title' => t('Yield Units'),
    '#default_value' => empty($node->recipe_yield_unit) ? t('Servings') : $node->recipe_yield_unit,
    '#size' => 16,
    '#maxlength' => 64,
    '#description' => t('The units for the yield field(ie servings, people, cans, cookies, etc).'),
    '#required' => FALSE,
  );
  return $form;
}

/**
 * Implements hook_menu().
 */
function recipe_menu() {

  // Add a tab on the recipe add screen for Recipe Import.
  // Need to add 'file path' because some modules render node/add/recipe/std
  // even though they shouldn't.
  $items['node/add/recipe/std'] = array(
    'title' => 'Standard entry',
    'weight' => 0,
    'file path' => drupal_get_path('module', 'node'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['node/add/recipe/import'] = array(
    'title' => 'Recipe Import',
    'description' => 'Allows you to create a recipe by pasting various formats into a big text box.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'recipe_import_form',
    ),
    'access callback' => 'recipe_import_myaccess',
    'access arguments' => array(
      'import recipes',
    ),
    'file' => 'recipe.admin.inc',
    'weight' => 1,
    'type' => MENU_LOCAL_TASK,
  );
  $items['recipe'] = array(
    'title' => 'Recipes',
    'page callback' => 'recipe_landing_page',
    'access arguments' => array(
      'access content',
    ),
    'file' => 'recipe.landing.page.inc',
  );
  $items['recipe/ingredient/autocomplete'] = array(
    'title' => 'Ingredient autocomplete',
    'page callback' => 'recipe_autocomplete_page',
    'type' => MENU_CALLBACK,
    'access arguments' => array(
      'access content',
    ),
    'file' => 'recipe.admin.inc',
  );
  $items['recipe/export'] = array(
    'page callback' => 'recipe_export',
    'type' => MENU_CALLBACK,
    'access arguments' => array(
      'access content',
    ),
    // lower level permissions are handled in recipe_export
    'file' => 'recipe.pages.inc',
  );

  // Recipe configuration and bulk import/export menus.
  $items['admin/config/content/recipe'] = array(
    'title' => 'Recipe',
    'description' => 'Settings that control how the recipe module functions.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'recipe_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'recipe.admin.inc',
    'weight' => 0,
  );
  $items['admin/structure/recipe'] = array(
    'title' => 'Recipe bulk import/export',
    'description' => 'Export/Import all recipes from this site to/from a supported format.',
    'page callback' => 'recipe_export_multi',
    'access callback' => 'recipe_export_multi_myaccess',
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'recipe.admin.inc',
  );
  $items['admin/structure/recipe/export_multi'] = array(
    'title' => 'Recipe bulk export',
    'description' => 'Export all recipes from this site into a supported output format.',
    'type' => MENU_DEFAULT_LOCAL_TASK | MENU_LOCAL_TASK,
  );
  $items['admin/structure/recipe/import_multi'] = array(
    'title' => 'Recipe bulk import',
    'description' => 'Import all recipes from this site into a supported output format.',
    'page callback' => 'recipe_import_multi',
    'access callback' => 'recipe_import_multi_myaccess',
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'recipe.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_block_info().
 */
function recipe_block_info() {
  $blocks = array();
  $blocks['summary'] = array(
    'info' => t('Recipe summary'),
  );
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function recipe_block_view($delta = '') {
  $block = array();
  switch ($delta) {
    case 'summary':
      if (user_access('access content')) {
        if (arg(0) == 'node' && is_numeric(arg(1)) && (arg(2) == '' || arg(2) == 'view')) {
          $node = node_load(arg(1));
          if ($node->type == 'recipe') {
            $block['subject'] = variable_get('recipe_summary_title', t('Summary'));
            $block['content'] = theme('recipe_summary', array(
              'node' => $node,
              'show_title' => FALSE,
              'show_yield_form' => TRUE,
            ));
            return $block;
          }
        }
      }
      break;
  }
}

/**
 * Implements hook_view().
 */
function recipe_view($node, $view_mode) {
  drupal_add_css(drupal_get_path('module', 'recipe') . '/recipe.css');
  if ($view_mode == 'full' && node_is_page($node)) {
    $breadcrumb = array();
    $breadcrumb[] = l(t('Home'), NULL);
    $breadcrumb[] = l(t('Recipes'), 'recipe');
    drupal_set_breadcrumb($breadcrumb);
  }

  // Prepare and sanitize node fields.
  recipe_node_sanitize($node);

  // Calculate yield and ingredient factor.
  // Get custom yield or default to a factor of 1.
  $node->recipe_yield = intval($node->recipe_yield);

  // Factor is calculated and added into the $node variable.
  $node->recipe_factor = 1;

  // check post variable to see if the yield form was posted.
  if ($node->recipe_yield != 0 && isset($_POST['op'])) {
    if ($_POST['op'] == t('Change')) {
      $node->recipe_factor = $_POST['custom_yield'] / $node->recipe_yield;
      $node->recipe_yield = $_POST['custom_yield'];
      $_POST = array();
    }
    elseif ($_POST['op'] == t('Halve')) {
      $node->recipe_factor = $_POST['custom_yield'] / 2 / $node->recipe_yield;
      $node->recipe_yield = $_POST['custom_yield'] / 2;
      $_POST = array();
    }
    elseif ($_POST['op'] == t('Double')) {
      $node->recipe_factor = $_POST['custom_yield'] * 2 / $node->recipe_yield;
      $node->recipe_yield = $_POST['custom_yield'] * 2;
      $_POST = array();
    }
    elseif ($_POST['op'] == t('Reset')) {
      $_POST = array();
    }
  }
  elseif (isset($node->recipe_custom_yield)) {
    $node->recipe_factor = $node->recipe_custom_yield / $node->recipe_yield;
    $node->recipe_yield = $node->recipe_custom_yield;
  }

  // If it is a teaser, you're done.
  // The teaser should have a full $node object, but not the $node->content render array.
  if ($view_mode == 'teaser') {
    return $node;
  }
  if (isset($node->in_preview) && $node->in_preview == 1) {
    $node->recipe_show_yield_form = FALSE;
  }
  $node->content['recipe_summary_box'] = array(
    '#markup' => theme('recipe_summary', array(
      'node' => $node,
      'show_yield_form' => isset($node->recipe_show_yield_form) ? $node->recipe_show_yield_form : TRUE,
    )),
  );
  return $node;
}

/**
 * Implements hook_node_view().
 */
function recipe_node_view($node, $view_mode) {
  if (isset($node->in_preview) && $node->in_preview == 1) {
    return;
  }
  if ($node->type == 'recipe' && $view_mode != 'rss' && $view_mode != 'teaser') {
    $formats = module_invoke_all('recipeio', 'export_single');
    $links = array();
    foreach ($formats as $key => $format) {
      $perm = isset($format['access arguments']) ? $format['access arguments'] : 'export recipes';
      if (user_access($perm)) {
        $links[$key] = array(
          'title' => $format['format_name'],
          'href' => "recipe/export/{$key}/{$node->nid}/" . $node->recipe_yield,
          'attributes' => array(
            'title' => $format['format_help'],
          ),
        );
      }
    }
    if (!empty($links)) {
      array_unshift($links, array(
        'title' => '<br/>' . t('Export to') . ':',
        'html' => TRUE,
      ));
      $node->content['links']['recipe'] = array(
        '#theme' => 'links__recipe__node',
        '#links' => $links,
        '#attributes' => array(
          'class' => array(
            'links',
            'inline',
          ),
        ),
      );
    }
  }
}

/**
 * Implements hook_field_info().
 */
function recipe_field_info() {
  return array(
    'ingredient_reference' => array(
      'label' => t('Ingredient reference'),
      'description' => t('This field stores a reference to a recipe ingredient.'),
      'default_widget' => 'recipe_ingredient_autocomplete',
      'default_formatter' => 'recipe_ingredient_default',
      'settings' => array(
        'ingredient_name_normalize' => 0,
      ),
      // Integrate with the Entity API.
      'property_type' => 'ingredient_reference',
      'property_callbacks' => array(
        'recipe_metadata_field_ingredient_property_callback',
      ),
    ),
  );
}

/**
 * Implements hook_field_settings_form().
 */
function recipe_field_settings_form($field, $instance, $has_data) {
  $settings = $field['settings'];
  $form = array();
  switch ($field['type']) {
    case 'ingredient_reference':
      $form['ingredient_name_normalize'] = array(
        '#type' => 'radios',
        '#title' => t('Ingredient name normalization'),
        '#default_value' => $settings['ingredient_name_normalize'],
        '#options' => array(
          t('Leave as entered'),
          t('Convert to lowercase'),
        ),
        '#description' => t('When recipes are entered, should ingredient names be converted to lowercase?'),
        '#required' => TRUE,
      );
      break;
  }
  return $form;
}

/**
 * Implements hook_field_widget_info().
 */
function recipe_field_widget_info() {
  return array(
    'recipe_ingredient_autocomplete' => array(
      'label' => t('Autocomplete ingredient widget'),
      'field types' => array(
        'ingredient_reference',
      ),
      'settings' => array(
        'default_unit' => '',
      ),
      'behaviors' => array(
        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
        'default value' => FIELD_BEHAVIOR_NONE,
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_settings_form().
 */
function recipe_field_widget_settings_form($field, $instance) {
  $settings = $instance['widget']['settings'];
  $form['default_unit'] = array(
    '#type' => 'select',
    '#title' => t('Default unit type for ingredients'),
    '#default_value' => $settings['default_unit'],
    '#options' => recipe_unit_options(),
    '#description' => t('The default unit for new ingredients on the recipe edit screen.'),
  );
  return $form;
}

/**
 * Implements hook_field_widget_form().
 */
function recipe_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {

  // Add recipe.css to style the form elements on a single line.
  drupal_add_css(drupal_get_path('module', 'recipe') . '/recipe.css');
  switch ($instance['widget']['type']) {
    case 'recipe_ingredient_autocomplete':

      // Strange, but html_entity_decode() doesn't handle &frasl;
      $quantity = isset($items[$delta]['quantity']) ? preg_replace('/\\&frasl;/', '/', recipe_ingredient_quantity_from_decimal($items[$delta]['quantity'], '{%d} %d&frasl;%d', TRUE)) : '';
      $element['quantity'] = array(
        '#type' => 'textfield',
        '#title' => t('Quantity'),
        '#default_value' => $quantity,
        '#size' => 8,
        '#maxlength' => 8,
        '#attributes' => array(
          'class' => array(
            'recipe-ingredient-quantity',
          ),
        ),
      );
      $element['unit_key'] = array(
        '#type' => 'select',
        '#title' => t('Unit'),
        '#default_value' => isset($items[$delta]['unit_key']) ? $items[$delta]['unit_key'] : $instance['widget']['settings']['default_unit'],
        '#options' => recipe_unit_options(),
        '#attributes' => array(
          'class' => array(
            'recipe-ingredient-unit-key',
          ),
        ),
      );
      $ingredient = isset($items[$delta]['iid']) ? recipe_load_ingredient($items[$delta]['iid']) : array();
      $element['name'] = array(
        '#type' => 'textfield',
        '#title' => t('Name'),
        '#default_value' => isset($ingredient->name) ? $ingredient->name : '',
        '#size' => 25,
        '#maxlength' => 128,
        '#autocomplete_path' => 'recipe/ingredient/autocomplete',
        '#attributes' => array(
          'class' => array(
            'recipe-ingredient-name',
          ),
        ),
      );
      $element['note'] = array(
        '#type' => 'textfield',
        '#title' => t('Note'),
        '#default_value' => isset($items[$delta]['note']) ? $items[$delta]['note'] : '',
        '#size' => 40,
        '#maxlength' => 255,
        '#attributes' => array(
          'class' => array(
            'recipe-ingredient-note',
          ),
        ),
      );
      $element['#element_validate'] = array(
        'recipe_ingredient_autocomplete_validate',
      );
      break;
  }
  return $element;
}

/**
 * Form element validate handler for recipe ingredient autocomplete element.
 */
function recipe_ingredient_autocomplete_validate($element, &$form_state) {
  if (empty($element['unit_key']['#value']) && !empty($element['name']['#value'])) {
    form_error($element['unit_key'], t('You must choose a valid unit.'));
    return;
  }

  // The autocomplete widget doesn't supply the iid. Search for it and create
  // the new ingredient, if necessary.
  $value = array();
  if ($name = $element['name']['#value']) {

    // Get the field settings.
    $field = field_widget_field($element, $form_state);

    // Don't convert to lowercase if there is a &reg; (registered trademark
    // symbol).
    if ($field['settings']['ingredient_name_normalize'] == 1 && !preg_match('/&reg;/', $name)) {
      $name = trim(strtolower($name));
    }

    // Try to find an iid with a name matching the entered ingredient name.
    // recipe_ingredient_id_from_name() creates the ingredient if not found.
    $value = array(
      'quantity' => $element['quantity']['#value'],
      'unit_key' => $element['unit_key']['#value'],
      'iid' => recipe_ingredient_id_from_name($name),
      'name' => $name,
      'note' => $element['note']['#value'],
      '_weight' => $element['_weight']['#value'],
    );
  }
  form_set_value($element, $value, $form_state);
}

/**
 * Implements hook_field_is_empty().
 */
function recipe_field_is_empty($item, $field) {
  if (!is_array($item) || empty($item['name'])) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_field_presave().
 */
function recipe_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  switch ($field['type']) {
    case 'ingredient_reference':
      foreach ($items as $delta => $item) {

        // Convert fractional ingredient quantities to decimal.
        $items[$delta]['quantity'] = round(recipe_ingredient_quantity_from_fraction($item['quantity']), 6);
      }
      break;
  }
}

/**
 * Implements hook_field_formatter_info().
 */
function recipe_field_formatter_info() {
  return array(
    'recipe_duration' => array(
      'label' => t('Recipe duration'),
      'field types' => array(
        'number_integer',
      ),
    ),
    'recipe_ingredient_default' => array(
      'label' => t('Default'),
      'field types' => array(
        'ingredient_reference',
      ),
      'settings' => array(
        'fraction_format' => t('{%d }%d&frasl;%d'),
        'unit_abbreviation' => 0,
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function recipe_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $element = array();

  // Create the recipe ingredient formatter settings form.
  if ($display['type'] == 'recipe_ingredient_default') {
    $element['fraction_format'] = array(
      '#type' => 'textfield',
      '#title' => t('Fractions display string'),
      '#default_value' => $settings['fraction_format'],
      '#size' => 35,
      '#maxlength' => 255,
      '#description' => t('How fractions should be displayed. Leave blank to display as decimals.<br />Each incidence of %d will be replaced by the whole number, the numerator, and the denominator in that order.<br />Anything between curly braces will not be displayed when the whole number is equal to 0.<br />Recommended settings are "{%d }%d&amp;frasl;%d" or "{%d }&lt;sup&gt;%d&lt;/sup&gt;/&lt;sub&gt;%d&lt;/sub&gt;"'),
    );
    $element['unit_abbreviation'] = array(
      '#type' => 'radios',
      '#title' => t('Ingredient unit display'),
      '#default_value' => $settings['unit_abbreviation'],
      '#options' => array(
        t('Abbreviation'),
        t('Full name'),
      ),
      '#description' => t('Display ingredient units like Tbsp or Tablespoon.'),
      '#required' => TRUE,
    );
  }
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function recipe_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $summary = '';
  if ($display['type'] == 'recipe_ingredient_default') {
    $summary = t('Fractions display string: @fraction_format', array(
      '@fraction_format' => $settings['fraction_format'],
    )) . '<br />';
    $summary .= t('Ingredient unit display: @unit_abbreviation', array(
      '@unit_abbreviation' => $settings['unit_abbreviation'],
    ));
  }
  return $summary;
}

/**
 * Implements hook_field_formatter_prepare_view().
 */
function recipe_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {

  // Attach recipe.css to style the ingredient field.
  drupal_add_css(drupal_get_path('module', 'recipe') . '/recipe.css');
  $iids = array();
  $ingredients = array();
  $unit_list = recipe_get_units();

  // Find every iid referenced by the entities.
  foreach ($entities as $id => $entity) {

    // Process recipe ingredient formatter views.
    if ($displays[$id]['type'] == 'recipe_ingredient_default') {
      foreach ($items[$id] as $delta => $item) {

        // Check for import preview mode which will have a name, but not an iid.
        if (!isset($item['name']) && isset($item['iid'])) {
          $iids[$item['iid']] = $item['iid'];
        }
      }
    }
  }

  // Load all of the ingredients found in the entities.
  foreach ($iids as $iid) {
    $ingredients[$iid] = recipe_load_ingredient($iid);
  }

  // Iterate through the entities again to attach the loaded ingredient data.
  foreach ($entities as $id => $entity) {
    $rekey = FALSE;

    // Process recipe ingredient formatter views.
    if ($displays[$id]['type'] == 'recipe_ingredient_default') {
      foreach ($items[$id] as $delta => $item) {

        // Check for import preview mode or if the ingredient could be loaded.
        if (isset($item['name'])) {

          // Add the ingredient data to the instance value.
          $items[$id][$delta]['link'] = '';

          // Add the unit data to the instance value.
          $items[$id][$delta]['unit'] = isset($unit_list[$item['unit_key']]) ? $unit_list[$item['unit_key']] : array();
        }
        elseif (isset($ingredients[$item['iid']])) {

          // Add the ingredient data to the instance value.
          $ingredient = $ingredients[$item['iid']];
          $items[$id][$delta]['name'] = $ingredient->name;
          $items[$id][$delta]['link'] = $ingredient->link;

          // Add the unit data to the instance value.
          $items[$id][$delta]['unit'] = isset($unit_list[$item['unit_key']]) ? $unit_list[$item['unit_key']] : array();
        }
        else {
          unset($items[$id][$delta]);
          $rekey = TRUE;
        }
      }
    }
    if ($rekey) {

      // Rekey the items array.
      $items[$id] = array_values($items[$id]);
    }
  }
}

/**
 * Implements hook_field_formatter_view().
 */
function recipe_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $elements = array();
  switch ($display['type']) {
    case 'recipe_duration':
      foreach ($items as $delta => $item) {
        $elements[$delta] = array(
          '#theme' => $display['type'],
          '#duration' => $item['value'],
        );
      }
      break;
    case 'recipe_ingredient_default':
      $fraction_format = $display['settings']['fraction_format'];
      foreach ($items as $delta => $item) {

        // Sanitize the name and note.
        $name = filter_xss($item['name'], array());
        $note = filter_xss($item['note'], array());
        if ($item['quantity'] > 0) {

          // Alter the quantity based on the factor from the custom yield form
          // and format it as a fraction.
          $item['quantity'] *= isset($entity->recipe_factor) ? $entity->recipe_factor : 1;
          $formatted_quantity = recipe_ingredient_quantity_from_decimal($item['quantity'], $fraction_format);
        }
        else {
          $formatted_quantity = '&nbsp;';
        }

        // Print the unit unless it has no abbreviation. Those units do not get
        // printed in any case.
        $unit_display = '';
        if (!empty($item['unit']['abbreviation'])) {
          $title = $item['quantity'] > 1 ? $item['unit']['plural'] : $item['unit']['name'];

          // Print the abbreviation if recipe_unit_display == 0.
          if ($display['settings']['unit_abbreviation'] == 0) {
            $unit_display = '<abbr ' . drupal_attributes(array(
              'title' => $title,
            )) . '>' . $item['unit']['abbreviation'] . '</abbr>';
          }
          else {
            $unit_display = $title;
          }
        }
        $elements[$delta] = array(
          '#theme' => 'recipe_ingredient',
          '#name' => $name,
          '#quantity' => $formatted_quantity,
          '#unit' => $unit_display,
          '#note' => $note,
          '#link' => $item['link'],
        );
      }
      break;
  }
  return $elements;
}

/**
 * Callback to alter the property info of ingredient fields.
 *
 * @see recipe_field_info()
 */
function recipe_metadata_field_ingredient_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
  $property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  unset($property['query callback']);
  $property['type'] = $field['cardinality'] != 1 ? 'list<struct>' : 'struct';
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  $property['property info'] = array(
    'ingredient' => array(
      'type' => 'text',
      'label' => t('Ingredient'),
      'getter callback' => 'recipe_metadata_field_ingredient_name_get',
      'setter callback' => 'recipe_metadata_field_ingredient_name_set',
      'required' => TRUE,
    ),
    'quantity' => array(
      'type' => 'decimal',
      'label' => t('Quantity'),
      'setter callback' => 'entity_property_verbatim_set',
    ),
    'unit' => array(
      'type' => 'token',
      'label' => t('Quantity units'),
      'options list' => 'recipe_metadata_field_ingredient_units',
      'getter callback' => 'recipe_metadata_field_ingredient_unit_get',
      'setter callback' => 'recipe_metadata_field_ingredient_unit_set',
    ),
    'note' => array(
      'type' => 'text',
      'label' => t('Preparation notes'),
      'setter callback' => 'entity_property_verbatim_set',
    ),
  );
}

/**
 * Callback for getting the ingredient name of ingredient fields.
 *
 * @see recipe_metadata_field_ingredient_property_callback()
 */
function recipe_metadata_field_ingredient_name_get($data, array $options, $name, $type, $info) {
  return recipe_load_ingredient($data['iid'])->name;
}

/**
 * Callback for setting the ingredient iid of ingredient fields.
 *
 * @see recipe_metadata_field_ingredient_property_callback()
 */
function recipe_metadata_field_ingredient_name_set(&$data, $name, $value, $langcode, $type, $info) {
  $data['iid'] = recipe_ingredient_id_from_name($value);
}

/**
 * Callback for getting the list of unit names for an ingredient field.
 *
 * @see recipe_metadata_field_ingredient_property_callback()
 */
function recipe_metadata_field_ingredient_units() {
  $units = array();
  foreach (recipe_get_units() as $key => $unit_data) {
    $units[$key] = $unit_data['name'];
  }
  return $units;
}

/**
 * Callback for getting the unit name of ingredient fields.
 *
 * @see recipe_metadata_field_ingredient_property_callback()
 */
function recipe_metadata_field_ingredient_unit_get($data, array $options, $name, $type, $info) {
  return recipe_metadata_field_ingredient_units()[$data['unit_key']];
}

/**
 * Callback for setting the unit key of ingredient fields.
 *
 * @see recipe_metadata_field_ingredient_property_callback()
 */
function recipe_metadata_field_ingredient_unit_set(&$data, $name, $value, $langcode, $type, $info) {
  $data['unit_key'] = $value;
}

/**
 * Returns a string for displaying an integer field as a recipe duration.
 */
function theme_recipe_duration($variables) {
  $duration = $variables['duration'];
  $hours = floor($duration / 60);
  $minutes = $duration % 60;
  $output = array();
  if ($hours > 0) {
    $output['hours'] = format_plural($hours, '1 hour', '@count hours');
  }
  if ($minutes > 0 || $duration == 0) {
    $output['minutes'] = format_plural($minutes, '1 minute', '@count minutes');
  }
  return implode(', ', $output);
}

/**
 * Returns HTML for displaying an ingredient field item.
 */
function theme_recipe_ingredient($variables) {
  $quantity = $variables['quantity'];
  $name = $variables['name'];
  $link = $variables['link'];
  $unit = $variables['unit'];
  $note = $variables['note'];

  // Display the name as a link if the link (ingredient node nid) is set.
  if (!empty($link)) {
    $name = l($name, 'node/' . $link);
  }

  // Concatenate the ingredient note to the name, if set.
  if (!empty($note)) {
    $name .= ' (' . $note . ')';
  }
  $output = '<span class="quantity-unit" property="schema:amount"> ' . $quantity . ' ' . $unit . '</span> <span class="ingredient-name" property="schema:name">' . $name . '</span>';
  return $output;
}

/**
 * Returns HTML for displaying the recipe summary box.
 */
function theme_recipe_summary($variables) {
  $node = $variables['node'];
  $show_title = isset($variables['show_title']) ? $variables['show_title'] : FALSE;
  $show_yield_form = isset($variables['show_yield_form']) ? $variables['show_yield_form'] : TRUE;

  // Construct the summary
  $output = '<div class="recipe-summary">';
  if ($show_title) {
    $output .= '<h2 class="title">' . t('Summary') . '</h2>';
  }
  $output .= '<table>';

  // Render the yield form.
  $yield_form = drupal_get_form('recipe_yield_form', $node, $show_yield_form);
  $output .= '<tr><th class="summary-title">' . t('Yield') . '</th><td class="summary-data">' . drupal_render($yield_form) . '</td></tr>';

  // Print the total time if the prep and cook times are set.
  if (isset($node->recipe_cook_time[LANGUAGE_NONE][0]['value']) && isset($node->recipe_prep_time[LANGUAGE_NONE][0]['value'])) {
    $duration = $node->recipe_cook_time[LANGUAGE_NONE][0]['value'] + $node->recipe_prep_time[LANGUAGE_NONE][0]['value'];
    $_text = theme('recipe_duration', array(
      'duration' => $duration,
    ));

    // Wrap the total time in RDFa markup.
    if (!empty($node->rdf_mapping['recipe_totaltime'])) {
      $attributes = rdf_rdfa_attributes($node->rdf_mapping['recipe_totaltime']);
      $attributes['content'] = array(
        recipe_duration_iso8601($duration),
      );
      $_text = theme('rdf_template_variable_wrapper', array(
        'content' => $_text,
        'attributes' => $attributes,
        'inline' => FALSE,
      ));
    }
    $output .= '<tr><th>' . t('Total time') . '</th><td>' . $_text . '</td></tr>';
  }
  $output .= '</table>';
  $output .= '</div>';
  return $output;
}

/**
 * Returns a cached array of recipe unit types.
 */
function recipe_unit_options() {
  static $options;
  if (!isset($options)) {
    $units = recipe_get_units(variable_get('recipe_preferred_system_of_measure_limit', 0));

    // Put in a blank so non-matching units will not validate and save.
    $options = array(
      '' => '',
    );
    foreach ($units as $unit_key => $unit) {
      $text = $unit['name'];
      if (!empty($unit['abbreviation'])) {
        $text .= ' (' . $unit['abbreviation'] . ')';
      }
      $options[$unit_key] = $text;
    }
  }
  return $options;
}

/**
 * Converts a recipe ingredient name to an ID.
 */
function recipe_ingredient_id_from_name($name) {
  static $cache;
  if (!isset($cache[$name])) {
    $ingredient_id = db_query("SELECT id FROM {recipe_ingredient} WHERE LOWER(name) = :name", array(
      ':name' => trim(strtolower($name)),
    ))
      ->fetchField();
    if (!$ingredient_id) {
      global $active_db;
      $node_link = db_query("SELECT nid FROM {node} n WHERE type = 'ingredient' && title = :title;", array(
        ':title' => $name,
      ))
        ->fetchField();
      if (!$node_link) {
        $node_link = 0;
      }
      db_insert('recipe_ingredient')
        ->fields(array(
        'name' => $name,
        'link' => $node_link,
      ))
        ->execute();

      // Get the ID that you just added.
      $ingredient_id = db_query("SELECT id FROM {recipe_ingredient} WHERE LOWER(name) = :name", array(
        ':name' => trim(strtolower($name)),
      ))
        ->fetchField();
    }
    $cache[$name] = $ingredient_id;
  }
  return $cache[$name];
}

/**
 * Converts an ingredient's quantity from decimal to fraction.
 */
function recipe_ingredient_quantity_from_decimal($ingredient_quantity, $fraction_format = '{%d} %d&frasl;%d', $edit_mode = FALSE) {
  if (strpos($ingredient_quantity, '.')) {
    $decimal = abs($ingredient_quantity);
    $whole = floor($decimal);
    $numerator = 0;
    $denominator = 1;
    $top_heavy = 0;
    $power = 1;
    $flag = 0;
    while ($flag == 0) {
      $argument = $decimal * $power;
      if ($argument == floor($argument)) {
        $flag = 1;
      }
      else {
        $power = $power * 10;
      }
    }

    // We have to workaround for repeating, non-exact decimals for thirds, sixths, ninths, twelfths.
    $overrides = array(
      '3333' => array(
        1,
        3,
      ),
      '6666' => array(
        2,
        3,
      ),
      '9999' => array(
        3,
        3,
      ),
      // thirds
      '1666' => array(
        1,
        6,
      ),
      '8333' => array(
        5,
        6,
      ),
      // sixths
      '1111' => array(
        1,
        9,
      ),
      '2222' => array(
        2,
        9,
      ),
      '4444' => array(
        4,
        9,
      ),
      '5555' => array(
        5,
        9,
      ),
      '7777' => array(
        7,
        9,
      ),
      '8888' => array(
        8,
        9,
      ),
      // ninths
      '0833' => array(
        1,
        12,
      ),
      '4166' => array(
        5,
        12,
      ),
      '5833' => array(
        7,
        12,
      ),
      '9166' => array(
        11,
        12,
      ),
    );

    // truncate the whole part to get just the fractional part
    $conversionstr = substr((string) ($decimal - floor($decimal)), 2, 4);
    if (array_key_exists($conversionstr, $overrides)) {
      if ($overrides[$conversionstr][0] == $overrides[$conversionstr][1]) {
        return $whole + 1;
      }
      $denominator = $overrides[$conversionstr][1];
      $numerator = floor($decimal) * $denominator + $overrides[$conversionstr][0];
    }
    else {
      $numerator = $decimal * $power;
      $denominator = $power;
    }

    // repeating decimals have been corrected
    $gcd = greatest_common_divisor($numerator, $denominator);
    $numerator = $numerator / $gcd;
    $denominator = $denominator / $gcd;
    $top_heavy = $numerator;
    $numerator = abs($top_heavy) - abs($whole) * $denominator;
    $ingredient_quantity = sprintf($fraction_format, $whole, $numerator, $denominator);
    if ($whole == 0 && strpos($ingredient_quantity, '{') >= 0) {

      // Remove anything in curly braces.
      $ingredient_quantity = preg_replace('/{.*}/', '', $ingredient_quantity);
    }
    else {

      // Remove just the curly braces, but keep everything between them.
      $ingredient_quantity = preg_replace('/{|}/', '', $ingredient_quantity);
    }

    // In edit mode we don't want to show html tags like <sup> and <sub>.
    if ($edit_mode) {
      $ingredient_quantity = strip_tags($ingredient_quantity);
    }
  }
  return filter_xss_admin(trim($ingredient_quantity));
}

/**
 * Finds the greatest common divisor of two numbers.
 */
function greatest_common_divisor($a, $b) {
  while ($b != 0) {
    $remainder = $a % $b;
    $a = $b;
    $b = $remainder;
  }
  return abs($a);
}

/**
 * Converts an ingredient's quantity from fraction to decimal.
 */
function recipe_ingredient_quantity_from_fraction($ingredient_quantity) {

  // Replace a dash separated fraction with a ' ' to normalize the input string.
  $ingredient_quantity = preg_replace('/^(\\d+)[\\-](\\d+)[\\/](\\d+)/', '${1} ${2}/${3}', $ingredient_quantity);
  if ($pos_slash = strpos($ingredient_quantity, '/')) {
    $pos_space = strpos($ingredient_quantity, ' ');

    // Can't trust $pos_space to be a zero value if there is no space
    // so set it explicitly.
    if ($pos_space === FALSE) {
      $pos_space = 0;
    }
    $whole = (int) substr($ingredient_quantity, 0, $pos_space);
    $numerator = (int) substr($ingredient_quantity, $pos_space, $pos_slash);
    $denominator = (int) substr($ingredient_quantity, $pos_slash + 1);
    $ingredient_quantity = $whole + $numerator / $denominator;
  }
  return $ingredient_quantity;
}

/**
 * Implements hook_validate().
 *
 * Errors should be signaled with form_set_error().
 */
function recipe_validate($node, $form, &$form_state) {
  $return = TRUE;
  if (!is_numeric($form_state['values']['recipe_yield']) || $form_state['values']['recipe_yield'] <= 0) {
    form_set_error('recipe_yield', t('Yield must be a valid positive integer.'));
    $return = FALSE;
  }
  return $return;
}

/**
 * Sanitizes recipe data for display.
 *
 * All recipe fields should be run through one of the Drupal data checks.
 */
function recipe_node_sanitize(&$node) {
  if (!empty($node->recipe_yield_unit)) {
    $node->recipe_yield_unit = filter_xss($node->recipe_yield_unit, array());
  }
}

/**
 * Form constructor for the custom yield form.
 *
 * @param stdClass $node
 *   The node object being displayed.
 * @param Bool $show_yield_form
 *   TRUE if the custom yield form should be displayed.
 */
function recipe_yield_form($form, &$form_state, $node, $show_yield_form) {

  // Don't render the custom yield textbox and submit buttons if disabled or shown in a block.
  if ($show_yield_form == FALSE || variable_get('recipe_summary_location', 0) == 1) {
    $form['yield'] = array(
      '#markup' => $node->recipe_yield,
    );

    // An html space is useful here since we don't have a separate theme function for this form.
    $form['_space'] = array(
      '#markup' => '&nbsp;',
    );
    $form['yield_unit'] = array(
      '#markup' => $node->recipe_yield_unit == '' ? t('Servings') : $node->recipe_yield_unit,
    );
  }
  else {
    $form['custom_yield'] = array(
      '#type' => 'textfield',
      '#default_value' => $node->recipe_yield,
      '#size' => 2,
      '#maxlength' => 4,
      '#attributes' => array(
        'class' => array(
          'recipe-yield-value',
        ),
      ),
    );
    $form['yield_unit'] = array(
      '#markup' => $node->recipe_yield_unit == '' ? t('Servings') : $node->recipe_yield_unit,
      '#suffix' => '<br/>',
    );
    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Change'),
      '#attributes' => array(
        'class' => array(
          'recipe-yield-change',
        ),
      ),
    );
    $form['reset'] = array(
      '#type' => 'submit',
      '#value' => t('Reset'),
      '#attributes' => array(
        'class' => array(
          'recipe-yield-reset',
        ),
      ),
    );
    $form['halve'] = array(
      '#type' => 'submit',
      '#value' => t('Halve'),
      '#attributes' => array(
        'class' => array(
          'recipe-yield-halve',
        ),
      ),
    );
    $form['double'] = array(
      '#type' => 'submit',
      '#value' => t('Double'),
      '#attributes' => array(
        'class' => array(
          'recipe-yield-double',
        ),
      ),
    );
  }
  return $form;
}

/**
 * Extends user_access to handle the case where no import formats are available.
 */
function recipe_import_myaccess($string, $account = NULL, $reset = FALSE) {

  // short circuit if there are no parsers available.
  $formats = module_invoke_all('recipeio', 'import_single');
  if (count($formats) == 0) {
    return FALSE;
  }

  // we have a format so continue to user_access
  return user_access($string, $account, $reset);
}

/**
 * Extends user_access to handle the case where no export formats are available.
 */
function recipe_export_multi_myaccess($string, $account = NULL, $reset = FALSE) {

  // short circuit if there are no parsers available.
  $formats = module_invoke_all('recipeio', 'export_multi');
  if (count($formats) == 0) {
    return FALSE;
  }

  // we have a format so continue to user_access
  return user_access($string, $account, $reset);
}

/**
 * Extends user_access to handle the case where no import formats are available.
 */
function recipe_import_multi_myaccess($string, $account = NULL, $reset = FALSE) {

  // short circuit if there are no parsers available.
  $formats = module_invoke_all('recipeio', 'import_multi');
  if (count($formats) == 0) {
    return FALSE;
  }

  // we have a format so continue to user_access
  return user_access($string, $account, $reset);
}

/**
 * Selects the latest recipes by created date.
 *
 * @return
 *   A database query result suitable for use the node_title_list.
 */
function recipe_get_latest($count = 5) {
  $select = db_select('node', 'n');
  $select
    ->addField('n', 'nid');
  $select
    ->addField('n', 'title');
  $select
    ->addField('n', 'sticky');
  $select
    ->addField('n', 'created');
  $select
    ->condition('n.type', 'recipe');
  $select
    ->condition('n.status', 1);
  $select
    ->orderBy('n.sticky', 'DESC');
  $select
    ->orderBy('n.created', 'DESC');
  $select
    ->range(0, $count);
  $select
    ->addTag('node_access');
  return $select
    ->execute();
}

/**
 * Sanitizes a string and encodes the &deg; symbol.
 */
function strip_html_and_encode_entities($string) {
  $string = filter_xss($string, array());
  $string = str_replace("&deg;", "", $string);
  return $string;
}

/**
 * Implements hook_views_api().
 */
function recipe_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'recipe') . '/includes',
  );
}

/**
 * Implements hook_field_extra_fields().
 *
 * This adds these fields to the manage fields UI for changing weights.
 */
function recipe_field_extra_fields() {
  $extra = array();
  $extra['node']['recipe'] = array(
    'form' => array(
      'recipe_yield' => array(
        'label' => t('Yield'),
        'description' => t('Recipe module element'),
        'weight' => -3,
      ),
      'recipe_yield_unit' => array(
        'label' => t('Yield units'),
        'description' => t('Recipe module element'),
        'weight' => -3,
      ),
    ),
    'display' => array(
      'recipe_summary_box' => array(
        'label' => t('Recipe summary box'),
        'description' => t('Recipe module element'),
        'weight' => -7,
      ),
    ),
  );
  return $extra;
}

/**
 * Checks a string for ISO-8859-1 chars and encodes them to UTF-8.
 *
 * @param string $in_str
 *   A string with possible ISO-8859-1 chars.
 * @return string
 *   A UTF8 string representation of $in_str.
 */
function fixEncoding($in_str) {
  $cur_encoding = mb_detect_encoding($in_str);
  if ($cur_encoding == "UTF-8" && mb_check_encoding($in_str, "UTF-8")) {
    return $in_str;
  }
  else {
    return utf8_encode($in_str);
  }
}

/**
 * Implements hook_rdf_namespaces().
 */
function recipe_rdf_namespaces() {
  return array(
    'schema' => 'http://schema.org/',
  );
}

/**
 * Implements hook_rdf_mapping().
 */
function recipe_rdf_mapping() {
  return array(
    array(
      'type' => 'node',
      'bundle' => 'recipe',
      'mapping' => array(
        'rdftype' => array(
          'schema:Recipe',
        ),
        'title' => array(
          'predicates' => array(
            'schema:name',
          ),
        ),
        'recipe_instructions' => array(
          'predicates' => array(
            'schema:instructions',
          ),
        ),
        'recipe_description' => array(
          'predicates' => array(
            'schema:summary',
          ),
        ),
        'recipe_prep_time' => array(
          'predicates' => array(
            'schema:prepTime',
          ),
          'datatype' => 'xsd:duration',
          'callback' => 'recipe_duration_iso8601',
        ),
        'recipe_cook_time' => array(
          'predicates' => array(
            'schema:cookTime',
          ),
          'datatype' => 'xsd:duration',
          'callback' => 'recipe_duration_iso8601',
        ),
        'recipe_totaltime' => array(
          'predicates' => array(
            'schema:totalTime',
          ),
        ),
        'recipe_yield' => array(
          'predicates' => array(
            'schema:yield',
          ),
        ),
        'recipe_ingredient' => array(
          'predicates' => array(
            'schema:ingredients',
          ),
        ),
      ),
    ),
  );
}

/**
 * Returns a duration string in ISO 8601 format.
 *
 * @param $duration
 *   An integer or array with 'value' element representing a time duration.
 * @return string
 *   A string representing a time duration in ISO 8601 format.
 */
function recipe_duration_iso8601($duration = 0) {
  if (is_array($duration) && isset($duration['value'])) {
    $duration = $duration['value'];
  }
  $hours = floor($duration / 60);
  $minutes = $duration % 60;
  $output = '';
  if ($hours > 0) {
    $output .= $hours . 'H';
  }
  if ($minutes > 0) {
    $output .= $minutes . 'M';
  }
  return empty($output) ? 'PT0M' : 'PT' . $output;
}

/**
 * Returns an array of units.
 *
 * WARNING:  Do not change keys once they are released without a migration path.
 *
 * This allows for unit string translations and the possibility of allowing for
 * unit level integration with other modules.
 *
 * For new units, use the same unit keys as the unitsapi module keys found in
 * units.xml.  NOTE: This is not possible for all units.
 *
 * @return
 *   An array of units.  $units = array('unit key'=> array(name=>, plural=>, abbreviation=>, system=>, type=>, aliases=>))
 */
function recipe_get_units($limit_to_preferred = 0) {
  $us_units = array(
    'cup' => array(
      'name' => t('cup'),
      'plural' => t('cups'),
      'abbreviation' => t('c', array(), array(
        'context' => 'abbreviation cup',
      )),
      'system' => 'us customary',
      'type' => t('volume'),
      'aliases' => array(),
    ),
    'us liquid pint' => array(
      'name' => t('pint'),
      'plural' => t('pints'),
      'abbreviation' => t('pt', array(), array(
        'context' => 'abbreviation pint',
      )),
      'system' => 'us customary',
      'type' => t('volume'),
      'aliases' => array(),
    ),
    'us liquid quart' => array(
      'name' => t('quart'),
      'plural' => t('quarts'),
      'abbreviation' => t('q', array(), array(
        'context' => 'abbreviation quart',
      )),
      'system' => 'us customary',
      'type' => t('volume'),
      'aliases' => array(),
    ),
    'us gallon' => array(
      'name' => t('gallon'),
      'plural' => t('gallons'),
      'abbreviation' => t('gal', array(), array(
        'context' => 'abbreviation gallon',
      )),
      'system' => 'us customary',
      'type' => t('volume'),
      'aliases' => array(),
    ),
    'pound' => array(
      'name' => t('pound'),
      'plural' => t('pounds'),
      'abbreviation' => t('lb', array(), array(
        'context' => 'abbreviation pound',
      )),
      'system' => 'us customary',
      'type' => t('weight'),
      'aliases' => array(),
    ),
    'ounce' => array(
      'name' => t('ounce'),
      'plural' => t('ounces'),
      'abbreviation' => t('oz', array(), array(
        'context' => 'abbreviation ounce',
      )),
      'system' => 'us customary',
      'type' => t('weight'),
      'aliases' => array(),
    ),
    'us fluid ounce' => array(
      'name' => t('fluid ounce'),
      'plural' => t('fluid ounces'),
      'abbreviation' => t('fl oz', array(), array(
        'context' => 'abbreviation fluid ounce',
      )),
      'system' => 'us customary',
      'type' => t('volume'),
      'aliases' => array(),
    ),
  );
  $si_units = array(
    'milliliter' => array(
      'name' => t('milliliter'),
      'plural' => t('milliliters'),
      'abbreviation' => t('ml', array(), array(
        'context' => 'abbreviation milliliter',
      )),
      'system' => 'si',
      'type' => t('volume'),
      'aliases' => array(),
    ),
    'centiliter' => array(
      'name' => t('centiliter'),
      'plural' => t('centiliters'),
      'abbreviation' => t('cl', array(), array(
        'context' => 'abbreviation centiliter',
      )),
      'system' => 'si',
      'type' => t('volume'),
      'aliases' => array(),
    ),
    'liter' => array(
      'name' => t('liter'),
      'plural' => t('liters'),
      'abbreviation' => t('l', array(), array(
        'context' => 'abbreviation liter',
      )),
      'system' => 'si',
      'type' => t('volume'),
      'aliases' => array(),
    ),
    'deciliter' => array(
      'name' => t('deciliter'),
      'plural' => t('deciliters'),
      'abbreviation' => t('dl', array(), array(
        'context' => 'abbreviation deciliter',
      )),
      'system' => 'si',
      'type' => t('volume'),
      'aliases' => array(),
    ),
    'milligram' => array(
      'name' => t('milligram'),
      'plural' => t('milligrams'),
      'abbreviation' => t('mg', array(), array(
        'context' => 'abbreviation milligram',
      )),
      'system' => 'si',
      'type' => t('weight'),
      'aliases' => array(),
    ),
    'gram' => array(
      'name' => t('gram'),
      'plural' => t('grams'),
      'abbreviation' => t('g', array(), array(
        'context' => 'abbreviation gram',
      )),
      'system' => 'si',
      'type' => t('weight'),
      'aliases' => array(),
    ),
    'centigram' => array(
      'name' => t('centigram'),
      'plural' => t('centigrams'),
      'abbreviation' => t('cg', array(), array(
        'context' => 'abbreviation centigram',
      )),
      'system' => 'si',
      'type' => t('weight'),
      'aliases' => array(),
    ),
    'kilogram' => array(
      'name' => t('kilogram'),
      'plural' => t('kilograms'),
      'abbreviation' => t('kg', array(), array(
        'context' => 'abbreviation kilogram',
      )),
      'system' => 'si',
      'type' => t('weight'),
      'aliases' => array(),
    ),
  );
  $common_units = array(
    'tablespoon' => array(
      'name' => t('tablespoon'),
      'plural' => t('tablespoons'),
      'abbreviation' => t('T', array(), array(
        'context' => 'abbreviation tablespoon',
      )),
      'system' => 'common',
      'type' => t('volume'),
      'aliases' => array(
        'tbsp',
        'tb',
      ),
    ),
    'teaspoon' => array(
      'name' => t('teaspoon'),
      'plural' => t('teaspoons'),
      'abbreviation' => t('t', array(), array(
        'context' => 'abbreviation teaspoon',
      )),
      'system' => 'common',
      'type' => t('volume'),
      'aliases' => array(
        'tsp',
      ),
    ),
    'slice' => array(
      'name' => t('slice'),
      'plural' => t('slices'),
      'abbreviation' => t('sli', array(), array(
        'context' => 'abbreviation slice',
      )),
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(),
    ),
    'clove' => array(
      'name' => t('clove'),
      'plural' => t('cloves'),
      'abbreviation' => t('clv', array(), array(
        'context' => 'abbreviation clove',
      )),
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(),
    ),
    'load' => array(
      'name' => t('loaf'),
      'plural' => t('loaves'),
      'abbreviation' => t('lf', array(), array(
        'context' => 'abbreviation loaf',
      )),
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(),
    ),
    'pinch' => array(
      'name' => t('pinch'),
      'plural' => t('pinches'),
      'abbreviation' => t('pn', array(), array(
        'context' => 'abbreviation pinch',
      )),
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(),
    ),
    'package' => array(
      'name' => t('package'),
      'plural' => t('packages'),
      'abbreviation' => t('pk', array(), array(
        'context' => 'abbreviation package',
      )),
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(
        'pack',
      ),
    ),
    'can' => array(
      'name' => t('can'),
      'plural' => t('cans'),
      'abbreviation' => t('cn', array(), array(
        'context' => 'abbreviation can',
      )),
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(
        'jar',
      ),
    ),
    'drop' => array(
      'name' => t('drop'),
      'plural' => t('drops'),
      'abbreviation' => t('dr', array(), array(
        'context' => 'abbreviation drop',
      )),
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(),
    ),
    'bunch' => array(
      'name' => t('bunch'),
      'plural' => t('bunches'),
      'abbreviation' => t('bn', array(), array(
        'context' => 'abbreviation bunch',
      )),
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(),
    ),
    'dash' => array(
      'name' => t('dash'),
      'plural' => t('dashes'),
      'abbreviation' => t('ds', array(), array(
        'context' => 'abbreviation dash',
      )),
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(),
    ),
    'carton' => array(
      'name' => t('carton'),
      'plural' => t('cartons'),
      'abbreviation' => t('ct', array(), array(
        'context' => 'abbreviation carton',
      )),
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(),
    ),
    'unit' => array(
      'name' => t('unit'),
      'plural' => t('units'),
      'abbreviation' => '',
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(
        'each',
        'ea',
        'whole',
      ),
    ),
    'unknown' => array(
      'name' => t('unknown'),
      'plural' => t('unknown'),
      'abbreviation' => '',
      'system' => 'common',
      'type' => t('indefinite'),
      'aliases' => array(),
    ),
  );
  if (variable_get('recipe_preferred_system_of_measure', 0) == 0) {

    // US system preferred.
    $units = array_merge($us_units, $common_units);
    if ($limit_to_preferred == 0) {
      $units = array_merge($units, $si_units);
    }
    uasort($units, 'unit_sort');
  }
  else {

    // SI system preferred.
    $units = array_merge($si_units, $common_units);
    if ($limit_to_preferred == 0) {
      $units = array_merge($units, $us_units);
    }
    uasort($units, 'unit_sort');
  }

  // Allow other modules to alter the units.
  drupal_alter('recipe_ing_units', $units);
  return $units;
}

/**
 * Returns the result of comparing two strings.
 */
function unit_sort($a, $b) {
  return strcmp($a['name'], $b['name']);
}

Functions

Namesort descending Description
fixEncoding Checks a string for ISO-8859-1 chars and encodes them to UTF-8.
greatest_common_divisor Finds the greatest common divisor of two numbers.
recipe_block_info Implements hook_block_info().
recipe_block_view Implements hook_block_view().
recipe_delete Implements hook_delete().
recipe_duration_iso8601 Returns a duration string in ISO 8601 format.
recipe_export_multi_myaccess Extends user_access to handle the case where no export formats are available.
recipe_field_extra_fields Implements hook_field_extra_fields().
recipe_field_formatter_info Implements hook_field_formatter_info().
recipe_field_formatter_prepare_view Implements hook_field_formatter_prepare_view().
recipe_field_formatter_settings_form Implements hook_field_formatter_settings_form().
recipe_field_formatter_settings_summary Implements hook_field_formatter_settings_summary().
recipe_field_formatter_view Implements hook_field_formatter_view().
recipe_field_info Implements hook_field_info().
recipe_field_is_empty Implements hook_field_is_empty().
recipe_field_presave Implements hook_field_presave().
recipe_field_settings_form Implements hook_field_settings_form().
recipe_field_widget_form Implements hook_field_widget_form().
recipe_field_widget_info Implements hook_field_widget_info().
recipe_field_widget_settings_form Implements hook_field_widget_settings_form().
recipe_form Implements hook_form().
recipe_get_latest Selects the latest recipes by created date.
recipe_get_units Returns an array of units.
recipe_help Implements hook_help().
recipe_import_multi_myaccess Extends user_access to handle the case where no import formats are available.
recipe_import_myaccess Extends user_access to handle the case where no import formats are available.
recipe_ingredient_autocomplete_validate Form element validate handler for recipe ingredient autocomplete element.
recipe_ingredient_id_from_name Converts a recipe ingredient name to an ID.
recipe_ingredient_quantity_from_decimal Converts an ingredient's quantity from decimal to fraction.
recipe_ingredient_quantity_from_fraction Converts an ingredient's quantity from fraction to decimal.
recipe_insert Implements hook_insert().
recipe_load Implements hook_load().
recipe_load_ingredient
recipe_menu Implements hook_menu().
recipe_metadata_field_ingredient_name_get Callback for getting the ingredient name of ingredient fields.
recipe_metadata_field_ingredient_name_set Callback for setting the ingredient iid of ingredient fields.
recipe_metadata_field_ingredient_property_callback Callback to alter the property info of ingredient fields.
recipe_metadata_field_ingredient_units Callback for getting the list of unit names for an ingredient field.
recipe_metadata_field_ingredient_unit_get Callback for getting the unit name of ingredient fields.
recipe_metadata_field_ingredient_unit_set Callback for setting the unit key of ingredient fields.
recipe_node_info Implements hook_node_info().
recipe_node_insert Implements hook_node_insert().
recipe_node_sanitize Sanitizes recipe data for display.
recipe_node_update Implements hook_node_update().
recipe_node_view Implements hook_node_view().
recipe_permission Implements hook_permission().
recipe_rdf_mapping Implements hook_rdf_mapping().
recipe_rdf_namespaces Implements hook_rdf_namespaces().
recipe_theme Implements hook_theme().
recipe_unit_options Returns a cached array of recipe unit types.
recipe_update Implements hook_update().
recipe_validate Implements hook_validate().
recipe_view Implements hook_view().
recipe_views_api Implements hook_views_api().
recipe_yield_form Form constructor for the custom yield form.
strip_html_and_encode_entities Sanitizes a string and encodes the &deg; symbol.
theme_recipe_duration Returns a string for displaying an integer field as a recipe duration.
theme_recipe_ingredient Returns HTML for displaying an ingredient field item.
theme_recipe_summary Returns HTML for displaying the recipe summary box.
unit_sort Returns the result of comparing two strings.

Constants