recipe_recipeML.module in Recipe 7.2
Enables importing and exporting of recipeML format recipes.
File
modules/recipe_recipeML.moduleView source
<?php
/**
* @file
* Enables importing and exporting of recipeML format recipes.
*/
/**
* Implements hook_recipeio().
*/
function recipe_recipeML_recipeio($type) {
$supported = array(
'export_single' => array(
'format_name' => t('recipeML'),
'callback' => 'recipe_recipeML_export_single',
'format_help' => t('Export to a recipeML based xml format.'),
),
'export_multi' => array(
'format_name' => t('recipeML'),
'callback' => 'recipe_recipeML_export_multi',
'format_help' => t('Export all recipes to recipeML based xml format.'),
),
'import_multi' => array(
'format_name' => t('recipeML'),
'callback' => 'recipe_recipeML_import_multi',
'format_help' => t('Import recipes from a recipeML based xml file.'),
),
);
if (isset($supported[$type])) {
return array(
'recipeml' => $supported[$type],
);
}
else {
return FALSE;
}
}
function recipe_recipeML_export_multi() {
$o = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<!DOCTYPE recipeml PUBLIC "-//FormatData//DTD RecipeML 0.5//EN" "http://www.formatdata.com/recipeml/recipeml.dtd">' . "\n" . '<recipeml version="0.5" generator="http://drupal.org/project/recipe">' . "\n";
// 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");
foreach ($result as $record) {
$o .= recipe_recipeML_export_single($record->nid, NULL, FALSE);
}
$o .= '</recipeml>';
drupal_add_http_header('Content-type', 'text/xml; charset=utf-8');
print $o;
}
function recipe_recipeML_export_single($nid = NULL, $yield = NULL, $add_content_header = TRUE) {
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;
$factor = 1;
if (isset($node->recipe_custom_yield)) {
$factor = $node->recipe_custom_yield / $node->recipe_yield;
$node->recipe_yield = $node->recipe_custom_yield;
}
$cat_string = '';
/*
$vocabs = taxonomy_get_vocabularies('recipe');
foreach ($vocabs as $vocab) {
$terms = taxonomy_node_get_terms_by_vocabulary($node, $vocab->vid);
foreach ( $terms as $term ) {
$term = array_shift($terms);
$cat_string .= $term->name . ', ';
}
$cat_string = substr($cat_string, 0, -2);
}
if ( $cat_string != '' ) {
$cat_string = '<categories>'. $cat_string .'</categories>';
}
*/
$description_items = field_get_items('node', $node, 'recipe_description');
$recipe_description = '';
if (!empty($description_items[0]['value'])) {
$recipe_description = my_xml_escape($description_items[0]['value'], TRUE);
}
$instructions_items = field_get_items('node', $node, 'recipe_instructions');
$recipe_instructions = '';
if (!empty($instructions_items[0]['value'])) {
$recipe_instructions = my_xml_escape($instructions_items[0]['value'], TRUE);
}
$notes_items = field_get_items('node', $node, 'recipe_notes');
$recipe_notes = '';
if (!empty($notes_items[0]['value'])) {
$recipe_notes = my_xml_escape($notes_items[0]['value'], TRUE);
}
$source_items = field_get_items('node', $node, 'recipe_source');
$recipe_source = '';
if (!empty($source_items[0]['value'])) {
$recipe_source = my_xml_escape($source_items[0]['value'], TRUE);
}
else {
$recipe_source = url('node/' . $node->nid, array(
'absolute' => TRUE,
));
}
$prep_time_items = field_get_items('node', $node, 'recipe_prep_time');
$recipe_prep_time = 0;
if (!empty($prep_time_items[0]['value'])) {
$recipe_prep_time = $prep_time_items[0]['value'];
}
$output = '';
if ($add_content_header == TRUE) {
$output .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<!DOCTYPE recipeml PUBLIC "-//FormatData//DTD RecipeML 0.5//EN" "http://www.formatdata.com/recipeml/recipeml.dtd">' . "\n" . '<recipeml version="0.5" generator="http://drupal.org/project/recipe">' . "\n";
}
$output .= "<recipe>\n" . "<head>\n" . '<title>' . my_xml_escape($node->title) . "</title>\n" . ' <version>' . date('m-d-Y', $node->changed) . "</version>\n" . ' <source>' . $recipe_source . '</source>' . "\n" . ' <yield><qty>' . floatval($node->recipe_yield) . '</qty><unit>' . my_xml_escape($node->recipe_yield_unit == '' ? t('Servings') : $node->recipe_yield_unit) . "</unit></yield>\n" . ' <preptime type="cooking"><time><qty>' . $recipe_prep_time . '</qty><timeunit>minutes</timeunit></time></preptime>' . "\n{$cat_string}" . "</head>\n" . '<description>' . $recipe_description . "</description>\n" . '<ingredients>';
$unit_list = recipe_get_units();
$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'];
foreach ($ingredient_items as $item) {
// Load the ingredient so we can print its name.
$ingredient = recipe_load_ingredient($item['iid']);
$name = my_xml_escape($ingredient->name);
$prep = '';
if (strlen($item['note']) > 0) {
$prep = '<prep>' . my_xml_escape($item['note']) . '</prep>';
}
if ($item['quantity'] > 0) {
$item['quantity'] *= $factor;
}
// 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'];
}
}
$output .= "\n<ing><amt><qty>" . $item['quantity'] . '</qty><unit>' . $unit_display . '</unit></amt><item>' . $name . "</item>{$prep}</ing>";
}
$output .= "\n" . "</ingredients>\n" . '<directions>' . $recipe_instructions . "</directions>\n" . '<note>' . $recipe_notes . "</note>\n" . "</recipe>\n";
if ($add_content_header == TRUE) {
$output .= '</recipeml>';
drupal_add_http_header('Content-type', 'text/xml; charset=utf-8');
}
return $output;
}
function my_xml_escape($string, $remove_html = FALSE) {
if ($remove_html) {
$string = filter_xss($string, array());
}
$chars = array(
'&' => '&',
'<' => '<',
'>' => '>',
'"' => '"',
'\'' => ''',
);
return str_replace(array_keys($chars), array_values($chars), $string);
}
function recipe_recipeML_import_multi() {
$o = drupal_get_form('recipe_recipeML_import_form');
return $o;
}
function recipe_recipeML_import_form($form_state) {
$form = array();
$form['#attributes'] = array(
'enctype' => "multipart/form-data",
);
$form['recipe_import_file'] = array(
'#type' => 'file',
'#title' => t('RecipeML File'),
'#default_value' => '',
'#size' => 34,
'#description' => t("A Recipe in RecipeML format, see http://www.formatdata.com/recipeml. Note: This will add taxonomy terms to the lightest weight recipe taxonomy."),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Import'),
);
return $form;
}
function recipe_recipeML_import_form_submit($form, &$form_state) {
global $recipes, $recipe, $data_string;
$data = '';
$validators = array(
'file_validate_extensions' => array(
'mxp xml',
),
);
if ($file = file_save_upload('recipe_import_file', $validators, FALSE, FILE_EXISTS_RENAME)) {
// Load the xml string.
$data = file_get_contents($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;
}
// Parse the data.
$xml_parser = drupal_xml_parser_create($data);
xml_set_element_handler($xml_parser, 'recipe_import_element_start', 'recipe_import_element_end');
xml_set_character_data_handler($xml_parser, 'recipe_import_element_data');
if (!xml_parse($xml_parser, $data, 1)) {
watchdog('recipe', 'Failed to parse RecipeML file: @error at line @line.', array(
'@error' => xml_error_string(xml_get_error_code($xml_parser)),
'@line' => xml_get_current_line_number($xml_parser),
), WATCHDOG_WARNING);
}
// Free the parser.
xml_parser_free($xml_parser);
if ($_POST['op'] == t('Import')) {
// $vocabs = taxonomy_get_vocabularies('recipe');
// list($lightest_vid, $vocab) = each($vocabs);
// reset($vocabs);
foreach ($recipes as $import_recipe_array) {
// $recipe->taxonomy = array();
/*
foreach ($recipe->_categories as $category) {
// Search the lightest weight recipe vocab for this term.
$term = recipe_get_term_by_name($category, $lightest_vid);
// You didn't find that term, so add it.
if ( $term == FALSE && isset($lightest_vid) ) {
$term = array('name' => $category, 'vid' => $lightest_vid);
drupal_set_message(t('Adding term %term_name', array('%term_name' => $category)));
taxonomy_save_term($term);
// Cast back to object so it's like the return value from recipe_get_term_by_name().
$term = (object)$term;
}
// You have the term now (existing or new), link it ink.
if ( isset($term) ) {
$recipe->taxonomy[] = $term->tid;
}
}
*/
if (($node = recipe_import_get_node($import_recipe_array)) != FALSE) {
node_save($node);
}
}
}
}
/**
* Call-back function used by the XML parser.
*/
function recipe_import_element_start($parser, $name, $attributes) {
global $recipe;
switch ($name) {
case 'RECIPE':
$recipe = array(
'title' => '',
'description' => '',
'yield' => '1',
'yield_unit' => 'Servings',
'cooktime' => 0,
'categories' => array(),
'ingredients' => array(),
'instructions' => '',
'notes' => '',
'source' => '',
'__region' => '',
);
break;
case 'INGREDIENTS':
$recipe['__region'] = 'ingredients';
break;
case 'ING':
$ingredient = array(
'ingredient_name' => '',
'ingredient_note' => '',
'quantity' => '',
'unit_name' => '',
'unit_key' => FALSE,
'ingredient_note' => '',
);
$recipe['ingredients'][] = $ingredient;
$recipe['__region'] = 'ING';
break;
case 'YIELD':
$recipe['__region'] = 'YIELD';
break;
case 'PREPTIME':
$recipe['__region'] = 'PREPTIME';
break;
}
}
/**
* Call-back function used by the XML parser.
*/
function recipe_import_element_end($parser, $name) {
global $recipes, $recipe, $data_string;
switch ($name) {
case 'RECIPE':
if ($recipe['yield'] == 0) {
$recipe['yield'] = 1;
}
if (empty($recipe['title'])) {
$recipe['title'] = "RecipeML auto title";
}
if (empty($recipe['description'])) {
$recipe['description'] = "RecipeML auto description.";
}
if (empty($recipe['notes'])) {
$recipe['notes'] = "Imported from RecipeML file";
}
$recipes[] = $recipe;
break;
case 'YIELD':
$recipe['__region'] = '';
break;
case "TITLE":
$recipe['title'] = trim($data_string);
break;
case "SOURCE":
$recipe['source'] = trim($data_string);
break;
case "CATEGORIES":
$xmlterms = explode(',', $data_string);
foreach ($xmlterms as $xmlterm) {
$recipe['categories'][] = trim($xmlterm);
}
break;
// QTY tag appears in more than one container tag.
case 'QTY':
if ($recipe['__region'] == 'YIELD') {
$recipe['yield'] = trim($data_string);
}
elseif ($recipe['__region'] == 'PREPTIME') {
$recipe['preptime'] = trim($data_string);
}
else {
$ing = array_pop($recipe['ingredients']);
$ing['quantity'] = trim($data_string);
$recipe['ingredients'][] = $ing;
}
break;
case 'UNIT':
// UNIT is contained in PREPTIME, YIELD, as well as ING->AMT.
if ($recipe['__region'] == 'ING') {
$ing = array_pop($recipe['ingredients']);
$ing['unit_name'] = trim($data_string);
// Try to match unit.
if ($unit_key = recipe_unit_fuzzymatch(trim($data_string))) {
$ing['unit_key'] = $unit_key;
}
else {
$ing['unit_key'] = 'unit';
if (strlen($ing['ingredient_name']) > 0) {
$ing['ingredient_name'] = $ing['ingredient_name'] . ' ' . trim($data_string);
}
else {
$ing['ingredient_name'] = trim($data_string);
}
}
$recipe['ingredients'][] = $ing;
}
break;
case 'ITEM':
$ing = array_pop($recipe['ingredients']);
if (strlen($ing['ingredient_name']) > 0) {
$ing['ingredient_name'] = trim($data_string) . ' ' . $ing['ingredient_name'];
}
else {
$ing['ingredient_name'] = trim($data_string);
}
$recipe['ingredients'][] = $ing;
break;
case 'DIRECTIONS':
$recipe['instructions'] .= trim($data_string);
break;
case 'STEP':
$recipe['instructions'] .= trim($data_string) . "\n";
break;
case 'NOTE':
$recipe['notes'] = trim($data_string);
break;
case 'SOURCE':
$recipe['source'] = trim($data_string);
break;
case 'PREP':
$ing = array_pop($recipe['ingredients']);
$ing['ingredient_note'] = trim($data_string);
$recipe['ingredients'][] = $ing;
case 'PREPTIME':
$recipe['__region'] = '';
break;
}
$data_string = '';
}
/**
* Call-back function used by the XML parser.
*/
function recipe_import_element_data($parser, $data) {
global $data_string;
$data_string .= $data;
}
function recipe_get_term_by_name($name, $vocab_id) {
$db_result = db_query(db_rewrite_sql("SELECT t.tid, t.* FROM {term_data} t WHERE t.vid=%d and LOWER(t.name) = LOWER('%s')", 't', 'tid'), $vocab_id, trim($name));
$result = array();
while ($term = db_fetch_object($db_result)) {
return $term;
}
return FALSE;
}
Functions
Name | Description |
---|---|
my_xml_escape | |
recipe_get_term_by_name | |
recipe_import_element_data | Call-back function used by the XML parser. |
recipe_import_element_end | Call-back function used by the XML parser. |
recipe_import_element_start | Call-back function used by the XML parser. |
recipe_recipeML_export_multi | |
recipe_recipeML_export_single | |
recipe_recipeML_import_form | |
recipe_recipeML_import_form_submit | |
recipe_recipeML_import_multi | |
recipe_recipeML_recipeio | Implements hook_recipeio(). |