recipe_mastercook4.module in Recipe 7
recipe_mastercook4.module - Enables importing and exporting of MasterCook4 format recipes.
File
includes/recipe_mastercook4.moduleView source
<?php
/**
* @file
* recipe_mastercook4.module - Enables importing and exporting of MasterCook4 format recipes.
*/
/**
* Implementation of hook_recipeio($type).
*/
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);
recipe_mastercook4_textify($node);
// 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) {
//prepare prepare time
$decimal_hours = $node->recipe_preptime / 60;
$hours = floor($decimal_hours);
$minutes = sprintf("%02d", floor(($decimal_hours - $hours) * 60));
$preptime = "{$hours}:{$minutes}";
$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;
}
$ingredients = '';
$unit_list = recipe_get_units();
foreach ($node->recipe_ingredients['ing'] as $key => $i) {
if ($i['quantity'] > 0) {
$i['quantity'] *= $factor;
}
else {
$i['quantity'] = ' ';
}
if (isset($unit_list[$i['unit_key']])) {
// Print the singular or plural term depending on the quantity.
$title = $i['quantity'] > 1 ? $unit_list[$i['unit_key']]['plural'] : $unit_list[$i['unit_key']]['name'];
}
else {
$title = $i['unit_key'];
}
// Print the abbreviation if recipe_unit_display says to or the abbreviation is blank (ie = Unit, which we don't print).
if (!isset($i['abbreviation']) && isset($unit_list[$i['unit_key']])) {
$i['abbreviation'] = $unit_list[$i['unit_key']]['abbreviation'];
}
if (empty($i['abbreviation'])) {
$i['abbreviation'] = ' ';
}
$i['str_unit'] = '';
if (variable_get('recipe_unit_display', 0) == 0 || $i['abbreviation'] == ' ') {
$i['str_unit'] = $i['abbreviation'];
}
else {
$i['str_unit'] = $title;
}
$ingredients .= format_mastercook4_ingredient($i);
}
// get the template string
$template = get_template();
// merge title
$template = str_replace("<<title>>", $node->title, $template);
// merge recipe by
$template = str_replace("<<recipeby>>", $node->recipe_source, $template);
// merge serving size
$template = str_replace("<<servingsize>>", $node->recipe_yield, $template);
// merge preptime
$template = str_replace("<<preptime>>", $preptime, $template);
// merge categories
$template = str_replace("<<categories>>", $categories, $template);
// merge ingredients
$template = str_replace("<<ingredients>>", $ingredients, $template);
// merge instructions
$template = str_replace("<<instructions>>", strip_html_and_encode_entities($node->recipe_instructions), $template);
// merge notes
if ($node->recipe_notes != '') {
$node->recipe_notes = "NOTES : " . strip_html_and_encode_entities($node->recipe_notes);
}
$template = str_replace("<<notes>>", $node->recipe_notes, $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";
return $template;
}
function format_mastercook4_ingredient($ingredient = NULL) {
$ingredient['quantity'] = recipe_ingredient_quantity_from_decimal($ingredient['quantity'], TRUE);
// no html entities
$ingredient['quantity'] = str_replace('⁄', '/', $ingredient['quantity']);
$fullingredient = strlen($ingredient['note']) > 0 ? $ingredient['name'] . ' -- ' . $ingredient['note'] : $ingredient['name'];
$fullingredient = strip_html_and_encode_entities($fullingredient);
$fullingredient = wordwrap($fullingredient, 66, "\n ");
$o = sprintf("%8s %-12s %s\n", $ingredient['quantity'], $ingredient['str_unit'], $fullingredient);
return $o;
}
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;
}
/*
* Mastercook is an old style text format, remove html.
*/
function recipe_mastercook4_textify(&$node) {
$node->title = filter_xss($node->title, array());
$node->recipe_source = filter_xss($node->recipe_source, array());
$node->recipe_instructions = filter_xss($node->recipe_instructions, array());
}