You are here

recipe_mastercook4.module in Recipe 7.2

Enables importing and exporting of MasterCook4 format recipes.

File

modules/recipe_mastercook4.module
View source
<?php

/**
 * @file
 * Enables importing and exporting of MasterCook4 format recipes.
 */

/**
 * Implements hook_recipeio().
 */
function recipe_mastercook4_recipeio($type) {
  $supported = array(
    'import_single' => array(
      'format_name' => t('MasterCook4'),
      'callback' => 'recipe_mastercook4_import_single',
      'format_help' => '',
    ),
    'export_single' => array(
      'format_name' => t('MasterCook4'),
      'callback' => 'recipe_mastercook4_export_single',
      'format_help' => t('Export to a recipe to a MasterCook(1-4 .mxp) based text format.'),
    ),
    'export_multi' => array(
      'format_name' => t('MasterCook4'),
      'callback' => 'recipe_mastercook4_export_multi',
      'format_help' => t('Export all recipes to a MasterCook(1-4 .mxp) based text format.'),
    ),
    'import_multi' => array(
      'format_name' => t('MasterCook4'),
      'callback' => 'recipe_mastercook4_import_multi',
      'format_help' => t('Import recipes from a MasterCook(1-4 .mxp) based text file.'),
    ),
  );
  if (isset($supported[$type])) {
    return array(
      'mastercook4' => $supported[$type],
    );
  }
  else {
    return FALSE;
  }
}
function recipe_mastercook4_export_multi() {

  // you should not be able to export unpublished recipes
  $result = db_query("SELECT n.nid from {node} n WHERE n.type='recipe' and n.status > 0 ORDER BY n.title");
  $o = '';
  foreach ($result as $record) {
    $o .= recipe_mastercook4_export_single($record->nid);
  }
  drupal_add_http_header('Content-type', 'text/plain; charset=utf-8');
  print $o;
}
function recipe_mastercook4_export_single($nid = NULL, $yield = NULL) {
  if ($nid === NULL) {
    drupal_set_message(t('Recipe not found.'));
    drupal_not_found();
    return;
  }
  $node = node_load($nid);

  // you should not be able to export unpublished recipes
  if ($node->status == 0) {
    drupal_access_denied();
    return;
  }

  // Set the custom yield so we can scale up/down the recipe quantities.
  $node->recipe_custom_yield = $yield;
  drupal_add_http_header('Content-type', 'text/plain; charset=utf-8');
  return merge_template($node);
}
function merge_template($node) {
  $categories = '';

  //prepare ingredients
  $factor = 1;
  if (isset($node->recipe_custom_yield)) {
    $factor = $node->recipe_custom_yield / $node->recipe_yield;
    $node->recipe_yield = $node->recipe_custom_yield;
  }
  $unit_list = recipe_get_units();

  // get the template string
  $template = get_template();

  // merge title
  $template = str_replace("<<title>>", filter_xss($node->title, array()), $template);

  // merge recipe by
  $source_items = field_get_items('node', $node, 'recipe_source');
  $recipe_source = '';
  if (!empty($source_items[0]['value'])) {
    $recipe_source = strip_html_and_encode_entities($source_items[0]['value']);
  }
  $template = str_replace("<<recipeby>>", $recipe_source, $template);

  // merge serving size
  $template = str_replace("<<servingsize>>", $node->recipe_yield, $template);

  // merge preptime
  $prep_time_items = field_get_items('node', $node, 'recipe_prep_time');
  $recipe_prep_time = '0:00';
  if (!empty($prep_time_items[0]['value'])) {
    $duration = $prep_time_items[0]['value'];
    $hours = floor($duration / 60);
    $minutes = sprintf("%02d", $duration % 60);
    $recipe_prep_time = $hours . ':' . $minutes;
  }
  $template = str_replace("<<preptime>>", $recipe_prep_time, $template);

  // merge categories
  $template = str_replace("<<categories>>", $categories, $template);

  // merge ingredients
  $ingredient_items = field_get_items('node', $node, 'recipe_ingredient');
  $ingredient_instance = field_read_instance('node', 'recipe_ingredient', 'recipe');
  $quantity_format = $ingredient_instance['display']['default']['settings']['fraction_format'];
  $abbreviation_setting = $ingredient_instance['display']['default']['settings']['unit_abbreviation'];
  $recipe_ingredients = '';
  foreach ($ingredient_items as $item) {

    // Load the ingredient so we can print its name.
    $ingredient = recipe_load_ingredient($item['iid']);
    $item['name'] = $ingredient->name;

    // Sanitize the name and note.
    $name = filter_xss($item['name'], array());
    $note = filter_xss($item['note'], array());

    // Format the ingredient quantity.
    if ($item['quantity'] > 0) {
      $quantity = recipe_ingredient_quantity_from_decimal($item['quantity'] * $factor, $quantity_format, TRUE);
      $quantity = str_replace('&frasl;', '/', $quantity);
    }
    else {
      $quantity = ' ';
    }

    // Print the unit unless it has no abbreviation. Those units do not get
    // printed in any case.
    $unit_display = '';
    $item['unit'] = isset($unit_list[$item['unit_key']]) ? $unit_list[$item['unit_key']] : array();
    if (!empty($item['unit']['abbreviation'])) {

      // Print the abbreviation if recipe_unit_display == 0.
      if ($abbreviation_setting == 0) {
        $unit_display = $item['unit']['abbreviation'];
      }
      else {
        $unit_display = $item['quantity'] > 1 ? $item['unit']['plural'] : $item['unit']['name'];
      }
    }

    // Add the ingredient text to the output.
    $recipe_ingredients .= recipe_mastercook4_format_ingredient($name, $quantity, $unit_display, $note);
  }
  $template = str_replace("<<ingredients>>", $recipe_ingredients, $template);

  // merge instructions
  $instructions_items = field_get_items('node', $node, 'recipe_instructions');
  $recipe_instructions = '';
  if (!empty($instructions_items[0]['value'])) {
    $recipe_instructions = strip_html_and_encode_entities($instructions_items[0]['value']);
  }
  $template = str_replace("<<instructions>>", $recipe_instructions, $template);

  // merge notes
  $notes_items = field_get_items('node', $node, 'recipe_notes');
  $recipe_notes = '';
  if (!empty($notes_items[0]['value'])) {
    $recipe_notes = 'NOTES : ' . strip_html_and_encode_entities($notes_items[0]['value']);
  }
  $template = str_replace("<<notes>>", $recipe_notes, $template);
  $description_items = field_get_items('node', $node, 'recipe_description');
  if (!empty($description_items[0]['value'])) {
    $recipe_description = "DESCRIPTION : " . strip_html_and_encode_entities($description_items[0]['value']) . "\n\n";
    $template = str_replace("<<description>>", $recipe_description, $template);
  }
  else {
    $template = str_replace("<<description>>\n", "", $template);
  }
  return $template;
}
function get_template() {
  $template = "\n                     *  Exported from  MasterCook  *\n\n<<title>>\n\nRecipe By     : <<recipeby>>\nServing Size  : <<servingsize>>   Preparation Time : <<preptime>>\nCategories    : <<categories>>\n\n  Amount  Measure       Ingredient -- Preparation Method\n--------  ------------  --------------------------------\n<<ingredients>>\n<<instructions>>\n\n                   - - - - - - - - - - - - - - - - - -\n\n<<notes>>\n<<description>>\n";
  return $template;
}

/**
 * Formats an ingredient for display as a MasterCook4 recipe.
 *
 * @param string $name
 *   The ingredient name.
 * @param string $quantity
 *   The ingredient quantity.
 * @param string $unit
 *   The units for the ingredient quantity.
 * @param string $note
 *   The ingredient preparation note.
 * @return string
 *   The formatted ingredient.
 */
function recipe_mastercook4_format_ingredient($name, $quantity = ' ', $unit = '', $note = '') {

  // Concatenate the ingredient note to the name, if set.
  if (!empty($note)) {
    $name .= ' -- ' . $note;
  }
  $name = wordwrap($name, 66, "\n                        ");
  $output = sprintf("%8s  %-12s  %s\n", $quantity, $unit, $name);
  return $output;
}
function recipe_mastercook4_import_multi() {
  $o = drupal_get_form('recipe_mastercook4_import_form');
  return $o;
}
function recipe_mastercook4_import_form($form_state) {
  $form = array();
  $form['#attributes'] = array(
    'enctype' => "multipart/form-data",
  );
  $form['recipe_import_file'] = array(
    '#type' => 'file',
    '#title' => t('MasterCook(1-4 .mxp) File'),
    '#default_value' => '',
    '#size' => 64,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Import'),
  );
  return $form;
}
function recipe_mastercook4_import_form_submit($form, &$form_state) {

  // save to a temp files
  $data = array();
  $validators = array(
    'file_validate_extensions' => array(
      'mxp xml',
    ),
  );
  if ($file = file_save_upload('recipe_import_file', $validators, FALSE, FILE_EXISTS_RENAME)) {
    $data = file($file->uri);
    drupal_set_message(t('The attached file was successfully uploaded'));
  }
  else {
    drupal_set_message(t('The attched file failed to upload.'), 'error');
    return;
  }
  $recipe_txt = '';
  foreach ($data as $line) {
    if (preg_match("/\\* +Exported +from/i", $line)) {
      $recipe_txt = trim($recipe_txt);

      // Save recipe
      if (strlen($recipe_txt) > 0) {
        $parsed_recipe_object = recipe_mastercook4_import_single($recipe_txt);
        if (strlen($parsed_recipe_object['title']) > 0) {
          if (($node = recipe_import_get_node($parsed_recipe_object)) != FALSE) {

            // Save the recipe.
            node_save($node);
          }
        }
      }

      // Clear recipe buffer.
      $recipe_txt = '';
    }
    $recipe_txt .= $line;
  }

  // Handle the last one needed.
  $parsed_recipe_object = recipe_mastercook4_import_single($recipe_txt);
  if (strlen($parsed_recipe_object['title']) > 0) {
    if (($node = recipe_import_get_node($parsed_recipe_object)) != FALSE) {
      node_save($node);
    }
  }
}
function recipe_mastercook4_import_single($recipe_txt = NULL) {

  // loose bad characters.
  $recipe_txt = fixEncoding($recipe_txt);

  // region constants
  $reg = array(
    'head' => 0,
    'title' => 1,
    'meta' => 2,
    'ingredients' => 3,
    'directions' => 4,
    'notes' => 5,
    'eor' => 6,
  );
  $recipe = array(
    'title' => '',
    'yield' => '1',
    'yield_unit' => 'Servings',
    'preptime' => 0,
    'cooktime' => 0,
    'categories' => array(),
    'ingredients' => array(),
    'instructions' => '',
    'notes' => '',
    'source' => '',
  );

  // A reference to the last ingredient added.
  $last_ingred_key = NULL;
  $region = $reg['head'];
  $recipe_lines = explode("\n", $recipe_txt);
  foreach ($recipe_lines as $line) {
    $trimmed_line = trim($line);

    // Head
    if ($region == $reg['head']) {

      // blank line in head section, move to next section.
      if ($trimmed_line == '') {
        $region++;
        continue;
      }
    }

    // Title
    if ($region == $reg['title']) {

      // Capture title.
      if ($trimmed_line != '') {
        $recipe['title'] = $trimmed_line;
      }
      else {

        // blank line in title section, move to next section.
        $region++;
        continue;
      }
    }
    if ($region == $reg['meta']) {

      // Get the source.
      if (preg_match('/Recipe +By *: *(.*)/i', $line, $matches)) {
        $recipe['source'] = $matches[1];
      }

      // Get the categories.
      if (preg_match('/Categories *: *(.*)/i', $line, $matches)) {
        $cat1 = trim(substr($matches[1], 0, 33));
        $cat2 = trim(substr($matches[1], 33, 33));
        if ($cat1 != '') {
          $recipe['categories'][] = $cat1;
        }
        if ($cat2 != '') {
          $recipe['categories'][] = $cat2;
        }
      }

      // Category continuation.
      if (count($recipe['categories']) > 0 && preg_match('/^ {16}(.*)/', $line, $matches)) {
        $cat1 = trim(substr($matches[1], 0, 33));
        $cat2 = trim(substr($matches[1], 33, 33));
        if ($cat1 != '') {
          $recipe['categories'][] = $cat1;
        }
        if ($cat2 != '') {
          $recipe['categories'][] = $cat2;
        }
      }

      // blank line in meta section, move to next section.
      if ($trimmed_line == '' || preg_match('/Amount +Measure +Ingredient +-- +Preparation Method/i', $line)) {
        $region++;
        continue;
      }
    }
    if ($region == $reg['ingredients']) {
      if (preg_match('/Amount +Measure +Ingredient +-- +Preparation Method/i', $line)) {

        // Do nothing.
      }
      elseif (preg_match('/-------- +------------ +--------------------------------/', $line)) {

        // Do nothing.
      }
      elseif ($trimmed_line != '') {
        $q = trim(substr($line, 0, 8));
        $u = trim(substr($line, 10, 12));
        $i = trim(substr($line, 24));

        // If you have an ingredient continuation, add note to previous ingredient.
        // Ingredient line continuation must start with a -- in the ingredient name position.
        if ($q == '' && $u == '' && $last_ingred_key != NULL && preg_match('/^ *--/i', $i)) {
          $recipe['ingredients'][$last_ingred_key]['ingredient_note'] .= ' ' . $i;
        }
        else {
          $ing = array(
            'ingredient_name' => '',
            'ingredient_note' => '',
            'quantity' => '',
            'unit_key' => '',
          );
          $ing['quantity'] = recipe_ingredient_quantity_from_fraction($q);
          $ing['unit_name'] = $u;
          if (preg_match('/(.*?) ?-- ?(.*)/', $i, $matches)) {
            $ing['ingredient_name'] = $matches[1];
            $ing['ingredient_note'] = $matches[2];
          }
          else {
            $ing['ingredient_name'] = $i;
          }
          $ing['unit_key'] = recipe_unit_fuzzymatch($ing['unit_name']);
          if ($ing['unit_key'] == FALSE) {
            $ing['ingredient_note'] = '!' . $ing['unit_name'] . ' ' . $ing['ingredient_note'];
            $ing['unit_key'] = 'unit';
            drupal_set_message(t('Could not find the ingredient units in %recipe (%line)', array(
              '%recipe' => $recipe['title'],
              '%line' => $line,
            )), 'warning');
          }

          // Look up the ingredient, if it is not found it will be added later at node_save.
          $ing['ingred_obj'] = recipe_ingredient_match($ing['ingredient_name']);
          $recipe['ingredients'][] = $ing;
          end($recipe['ingredients']);
          $last_ingred_key = key($recipe['ingredients']);
        }
      }
      else {

        // blank line in ingredient section, move to next section.
        $region++;
        continue;
      }
    }
    elseif ($region == $reg['directions']) {
      if (preg_match('/- - - - - - - - - - - - - - - - - -/', $line)) {
        $region++;
        continue;
      }
      if (preg_match('/^Notes: +(.*)/i', $line, $matches)) {
        $recipe['notes'] .= $matches[1] . "\n";
        $region++;
        continue;
      }
      else {
        $recipe['instructions'] .= $line . "\n";
      }
    }
    elseif ($region == $reg['notes']) {
      $recipe['notes'] .= $line . "\n";
    }
  }
  return $recipe;
}