'Map', 'page callback' => 'feedapi_mapper_page', 'page arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'access callback' => 'feedapi_mapper_access_mapper', 'access arguments' => array(1), ); $items['node/%node/map/delete/%'] = array( 'title' => 'Delete', 'page callback' => 'drupal_get_form', 'page arguments' => array('feedapi_mapper_delete_form', 1, 4), 'type' => MENU_CALLBACK, 'access callback' => 'feedapi_mapper_access_mapper', 'access arguments' => array(1), ); foreach (node_get_types() as $type) { $type_url_str = str_replace('_', '-', $type->type); $items['admin/content/node-type/'. $type_url_str .'/map'] = array( 'title' => 'Map', 'page callback' => 'feedapi_mapper_page', 'page arguments' => array(3), 'load arguments' => array(3), 'type' => MENU_LOCAL_TASK, 'access callback' => 'feedapi_mapper_access_mapper', 'access arguments' => array(3), ); $items['admin/content/node-type/'. $type_url_str .'/map/delete/%'] = array( 'title' => 'Delete', 'page callback' => 'drupal_get_form', 'page arguments' => array('feedapi_mapper_delete_form', 3, 6), 'load arguments' => array(3), 'type' => MENU_LOCAL_TASK, 'access callback' => 'feedapi_mapper_access_mapper', 'access arguments' => array(3), ); } return $items; } /** * Implementation of hook_nodeapi(). */ function feedapi_mapper_nodeapi(&$node, $op, $teaser, $page) { switch ($op) { case 'prepare': if (isset($node->feedapi) && $node->feedapi->feed_item) { _feedapi_mapper_map($node); } break; } } /** * Implementation of hook_theme(). */ function feedapi_mapper_theme() { return array( 'feedapi_mapper_form' => array( 'arguments' => array('form'), ), 'feedapi_mapper_descriptions' => array( 'arguments' => array('descriptions'), ), ); } /** * Theming function for outputting result of _feedapi_mapper_get_field_mappers_descriptions(). * @param $descriptions * Result of _feedapi_mapper_get_field_mappers_descriptions(). * @return * HTML output. */ function theme_feedapi_mapper_descriptions($descriptions) { $output = '
'. print_r(_feedapi_mapper_truncate_array($merged_item), TRUE) .'', ); } if ($descriptions = theme('feedapi_mapper_descriptions', $descriptions_filtered)) { $form['descriptions'] = array( '#type' => 'fieldset', '#title' => t('Description of available mappers'), '#description' => t('This is a list of mappers available for the feed item content type for this feed.'), '#tree' => TRUE, ); $form['descriptions']['descriptions'] = array( '#type' => 'markup', '#value' => $descriptions, ); } return $form; } /** * Validate hook. */ function feedapi_mapper_form_validate($form, &$form_state) { if (empty($form_state['values']['source'])) { form_set_error('source', t('You need to specify a valid source from the dropdown list.')); } elseif (empty($form_state['values']['target'])) { form_set_error('target', t('You need to specify a valid destination from the dropdown list.')); } } /** * Submit hook. */ function feedapi_mapper_form_submit($form, &$form_state) { $param = $form['#node']->nid ? $form['#node']->nid : $form['#node']->type; feedapi_mapper_add_mapping($param, $form_state['values']['source'], $form_state['values']['target']); } /** * Add an additional map to a given mapping. * * @param $param * Either a node id of a node type. Used to determine storage. * @param $source * A serialized string mapping to the source feed element. * @param $target * A serialized string mapping to the target node element. */ function feedapi_mapper_add_mapping($param, $source, $target) { if (is_numeric($param)) { $map = unserialize(db_result(db_query('SELECT mapping FROM {feedapi_mapper} WHERE nid = %d', $param))); if (is_array($map)) { $map[$source] = $target; db_query('UPDATE {feedapi_mapper} SET mapping = \'%s\' WHERE nid = %d', serialize($map), $param); } else { $map = array($source => $target); db_query("INSERT INTO {feedapi_mapper} (nid, mapping) VALUES (%d, '%s')", $param, serialize($map)); } } else if (is_string($param)) { $variable = 'feedapi_mapper_mapping_'. $param; $map = variable_get($variable, array()); $map[$source] = $target; variable_set($variable, $map); } } function feedapi_mapper_delete_mapping($param, $source) { if (is_numeric($param)) { if ($map = unserialize(db_result(db_query('SELECT mapping FROM {feedapi_mapper} WHERE nid = %d', $param)))) { unset($map[$source]); db_query('UPDATE {feedapi_mapper} SET mapping = \'%s\' WHERE nid = %d', serialize($map), $param); } } else if (is_string($param)) { $variable = 'feedapi_mapper_mapping_'. $param; $map = variable_get($variable, array()); unset($map[$source]); variable_set($variable, $map); } } /** * Retrieve mapping from db. * @param $param * node id or node type * @return * Associative array in the format * array( * element_path1 => node_field1, * element_path2 => node_field2, * ); * where element_pathx and node_fieldx are both serialized arrays. */ function _feedapi_mapper_load_mapping($param) { static $mappings; if (is_numeric($param)) { if (isset($mappings[$param])) { return $mappings[$param]; } if ($mapping = db_result(db_query('SELECT mapping FROM {feedapi_mapper} WHERE nid = %d', $param))) { $mappings[$param] = unserialize($mapping); return $mappings[$param]; } } else if (is_string($param)) { return variable_get('feedapi_mapper_mapping_'. $param, array()); } } /** * Load mapper implementations. */ function _feedapi_mapper_load_mappers() { static $loaded = FALSE; if (!$loaded) { // Load all feedapi mapper implementations from ./mappers $path = drupal_get_path('module', 'feedapi_mapper') .'/mappers'; $files = drupal_system_listing('.*\.inc$', $path, 'name', 0); foreach ($files as $file) { require_once("./$file->filename"); } // Rebuild cache. module_implements('', FALSE, TRUE); } $loaded = TRUE; } /** * @todo: move this function to feedapi as feedapi_parse(). */ function _feedapi_parse($feed) { return _feedapi_call_parsers($feed, $feed->parsers, $feed->half_done ? FALSE : TRUE); } /** * Simplifies options->raw item on feed item. */ function _feedapi_mapper_simplify_raw($item, $parsers) { if (isset($item['options']['raw'])) { if (in_array('parser_simplepie', $parsers)) { $item['options']['raw'] = parser_simplepie_simplify_raw_item($item['options']['raw']); } } return $item; } /** * Returns all feed items on node as one merged item. */ function _feedapi_mapper_get_items_merged($node) { if ($node->feed) { $feed = _feedapi_parse($node->feed); // Convert items to array. $items = _feedapi_mapper_obj2array($feed->items); // Merge items to one item. $merged = NULL; if (is_array($items)) { $i = 0; foreach ($items as $item) { if ($i++ > 10) { break; } $merged = _feedapi_mapper_array_merge_recursive($item, $merged); } } return $merged; } } /** * Sister function of _feedapi_mapper_get_feed_elements(). * Returns array in same format. Only difference: does not take * a real feed that it analyzes, but returns some standard elements. * * Standard elements: * * options->original_author->name * options->original_author->picture * options->original_author * options->tags * * Implement hook_feedapi_mapper_elements() to add custom standard * elements. * * @see hook_feedapi_mapper_elements(). * * @return * Array in the format array('pathname' => 'serializedpath') */ function _feedapi_mapper_get_standard_elements() { $paths = array(); $paths[] = array('options', 'original_author', 'name'); $paths[] = array('options', 'original_author', 'picture'); $paths[] = array('options', 'original_author'); $paths[] = array('options', 'tags'); // hook_feedapi_mapper_elements() - allow other modules to add paths. // These should be in the exact same format as the paths above, i.e.: // array(array('foo', 'bar'), array('fnargle', 'bargle'),); foreach (module_implements('feedapi_mapper_elements') as $module) { $result = call_user_func($module .'_feedapi_mapper_elements'); if (is_array($result)) { $paths = array_merge($paths, $result); } } foreach ($paths as $path) { $elements[implode('->', $path)] = serialize($path); } return $elements; } /** * Takes a feed item and retrieves paths to all elements. * Use a merged feed item (_feedapi_mapper_get_items_merged()) for best results. * * @return * Array in the format array('pathname' => 'serializedpath') */ function _feedapi_mapper_get_feed_elements($merged_item) { // Retrieve paths to elements in merged item. // Stick them into an array where the key is the serialized path and the value is the element name. $elements = array(); while (count($merged_item)) { $path = array(); $path = _feedapi_mapper_next_element_path($merged_item, $path, TRUE); if ($path == FALSE) { break; } $elements[implode('->', $path)] = serialize($path); } return $elements; } /** * Traverse an associative array and look for path to first leaf. * If found, unset leaf and return path to it. * * @return * A path to a leaf element in the format * array(path, to, leaf, element); */ function _feedapi_mapper_next_element_path(&$items, &$path, $reset_count = FALSE) { // This recursion is a bit shaky. Put on breaks. static $i = 0; $i++; if ($reset_count) { $i = 0; } if ($i > 200) { drupal_set_message(t('Error in recursion _feedapi_mapper_next_element_path()'), 'error'); return FALSE; } foreach ($items as $key => $value) { $path[] = $key; // Recurse if value is array and if it contains elements. if (is_array($items[$key]) && count($items[$key])) { // Arrays with keys of 0 are not considered collections of same items - reached a leaf. if (isset($items[$key][0])) { unset($items[$key]); return $path; } else if ($result_path = _feedapi_mapper_next_element_path($items[$key], $path)) { // Leaf was found, pass it up. return $result_path; } } else { // Reached leaf, unset it and return path to it. unset($items[$key]); return $path; } } // No leaves found in this pass. return FALSE; } /** * Converts a multidemensional associative array with interdispersed objects into * an associative array only. */ function _feedapi_mapper_obj2array($items) { if (is_object($items)) { $items = (array) $items; } if (is_array($items)) { foreach ($items as $key => $value) { $items[$key] = _feedapi_mapper_obj2array($value); } } return $items; } /** * Truncates all strings in cascaded array. */ function _feedapi_mapper_truncate_array($array) { foreach ($array as $k => $v) { if (is_string($v)) { $array[$k] = strip_tags($v); $array[$k] = truncate_utf8($array[$k], 200, TRUE, TRUE); } else if (is_array($v)) { $array[$k] = _feedapi_mapper_truncate_array($v); } } return $array; } /** * Like array_merge_recursive. Only difference: does NOT merge * two different keys into an array, but merges key on key. * Argument 1 always has to be an array. */ function _feedapi_mapper_array_merge_recursive($array1, $array2) { $result = NULL; foreach ($array1 as $k => $v) { if (is_array($array1[$k])) { $result[$k] = _feedapi_mapper_array_merge_recursive($array1[$k], isset($array2[$k]) ? $array2[$k] : NULL); } else { $result[$k] = $array1[$k]; } } if (is_array($array2)) { foreach ($array2 as $k => $v) { if (is_array($array2[$k])) { $result[$k] = _feedapi_mapper_array_merge_recursive($array2[$k], isset($array1[$k]) ? $array1[$k] : NULL); } else { $result[$k] = $array2[$k]; } } } return $result; } /** * Get field mappers for a given node type. Returns an array of all * feed mappers that are applicable to this node type. * @param $node_type * Valid node type. * @return * Array of fields that are mappable for this content type. */ function _feedapi_mapper_get_field_mappers($node_type) { $node = new stdClass(); $node->type = $node_type; // Load all available mappers and create an array of fields available as mapping target. _feedapi_mapper_load_mappers(); $modules = module_implements('feedapi_mapper'); foreach ($modules as $module) { if ($fields = module_invoke($module, 'feedapi_mapper', 'list', $node)) { foreach ($fields as $field_name => $sub_fields) { $field_label = is_string($sub_fields) ? $sub_fields : $field_name; $field_category = t('Map to @field (@module)', array('@field' => $field_label, '@module' => $module)); if (is_array($sub_fields)) { foreach ($sub_fields as $sub_field_id => $sub_field_name) { $field_mappers[$field_category][serialize(array($module, $field_name, $sub_field_id))] = $field_category .': '. $sub_field_name; } } else { $field_mappers[serialize(array($module, $field_name))] = $field_category; } } } } return $field_mappers; } /** * Returns descriptions for all mappable fields of given node type. * @return * Array in the format * array('field_name_a' => * array('module_name_a' => 'Descriptive text'), * 'module_name_b' => ...), * 'field_name_b' => array(...), * ); */ function _feedapi_mapper_get_field_mappers_descriptions($node_type) { $node = new stdClass(); $node->type = $node_type; // Load all available mappers and create an array of fields available as mapping target. _feedapi_mapper_load_mappers(); $modules = module_implements('feedapi_mapper'); $descriptions = array(); foreach ($modules as $module) { if ($description = module_invoke($module, 'feedapi_mapper', 'describe', $node)) { $descriptions[$module] = $description; } } return $descriptions; }