Build Your Own Mapper with the Feed Element Mapper
How To Create A Mapper
For those of you who after reading my last post on the Feed Element Mapper are now interested in the actual works of the module, below is a step by step guide on how to build your own mapper.
But first, three things needed to happen before we were able to create the map-any-element-to-CCK feature:
- Jess from WETA poked me about it for their TV and Radio feeds at the Drupal Meetup in DC on Tuesday :)
- FeedAPI's parser_simplepie now passes on the raw feed item, and with that anything it detects on the feed, on feed_item->options->raw
- Feed Element Mapper offers a generic CCK mapper
From those three, the one I would like to explain a bit better is the third because it shows how easy it is to write your own mapper so you can map any feed item element identified by FeedAPI to virtually any property of a node. Let me explain how I built this mapper:
1)
This is the empty implementation of hook_feedapi_mapper(). It's just sitting there and doing nothing. In this case, it' the implementation for the content module, the hook starts with content_.
function content_feedapi_mapper($op, $node, $field_name,
$feed_element = array(), $sub_field = '') {
}
- $op tells us which operation is being executed ('describe', 'list,' or 'map').
- $node is the node object we're operating on.
- $field_name is the name of the field on the node we are being called with (e. g. 'field_severity' for $node->field_severity, or 'taxonomy' for $node->taxonomy).
- $feed_element is only set on $op == 'map' and contains the exact element that has been mapped to the given field. This element can be a string, a number or an array of string or numbers.
- $sub_field is advanced stuff - it contains a value only if you offer more than one mapping target on $op == 'list', which we won't do here (e. g. a taxonomy mapper offering a mapping for each vocabulary).
2)
Now, this hook is being called for every field Feed Element Mapper finds on a node (it uses the form of the content type of the feed item node configured for the feed for doing this). In this example, I would like to write a generic mapper for any field that starts with 'field_', so I need to make sure that I only return stuff if I'm on the right field:
function content_feedapi_mapper($op, $node, $field_name,
$feed_element = array(), $sub_field = '') {
if (strpos($field_name, 'field_') === 0) {
}
}
Ok, that was easy.
3)
Feed Element Mapper calls the hook on three occasions:
- 'describe' - here you can return a string that describes what this mapper does
- 'list' - here you can list sub fields you want to offer mappings to (e. g. a taxonomy implementation would list here all vocabularies) or if you only map to a single variable, return TRUE
- 'map' - this is where the actual mapping happens
This calls for the mighty switch statement (yep, it does, but as yched pointed out in the comments, we just use if/elseif here :), here we go:
function content_feedapi_mapper($op, $node, $field_name,
$feed_element = array(), $sub_field = '') {
if (strpos($field_name, 'field_') === 0) {
if ($op == 'describe') {
}
else if ($op == 'list') {
}
else if ($op == 'map') {
}
}
}
4)
Now let's add a description to 'describe' and return TRUE on 'list'.
function content_feedapi_mapper($op, $node, $field_name,
$feed_element = array(), $sub_field = '') {
if (strpos($field_name, 'field_') === 0) {
if ($op == 'describe') {
return t('Maps a string or a number to this CCK field.');
}
else if ($op == 'list') {
// We just return true here, as any element mapped
// to this field will be mapped to field_name[0]['value']
return TRUE;
}
else if ($op == 'map') {
}
}
}
5)
And here comes the fun part - the mapping. When Feed Element Mapper calls us here, it provides us with the field and the exact feed item element the user has mapped. Also, $node will contain the node object that represents the feed item in Drupal. So all we need to do is grab the feed item element and stick it on the right place on the node. Here is how this plays out for CCK:
function content_feedapi_mapper($op, $node, $field_name,
$feed_element = array(), $sub_field = '') {
if (strpos($field_name, 'field_') === 0) {
if ($op == 'describe') {
return t('Maps a string or a number to this CCK field.');
}
else if ($op == 'list') {
return TRUE;
}
else if ($op == 'map') {
if (!is_array($feed_element)) {
$field = $node->$field_name;
$field[0]['value'] = $feed_element;
$node->$field_name = $field;
}
return $node;
}
}
}
You can look at the original code here.
I hope this little tutorial-style explanation made sense. If you're all fired up now and can't wait to get your hands dirty with your own mapper, here is what's missing: a mapper for the date module ; )
8 Comments
Mapping to node status (published) and sticky fields
feedapi_mapper out of the box will do most of what we were looking for which is great! However, we would like to extend it with some functionality to publish and un-publish nodes aswell as set the sticky flag based on boolean values in a feed. I thought the method outlined above would allow me to specify these as fields to map to but the scope only seems to be the CCK fields I've activated on the target node type.
Is there a clever way in which I can force status and sticky to be listed as a mapping destination which I can handle when the hook is fired with 'map'.
Cheers,
Paul
Hi Paul, The answer is you'd
Hi Paul,
The answer is you'd have to build your own mapper implementation following the instructions above.
Shouldn't be hard to do...
a mapper would definitely be
a mapper would definitely be a great help
Thanks for this writeup (and
Thanks for this writeup (and for the previous ones), alex.
Could you simply elaborate on the
if (!is_array($feed_element)) {part ?When would $feed_element be an array, and why don't we do anything then ?
Also, "This calls for the mighty switch statement", yet the code is if / elseif ;-)
> Also, "This calls for the
> Also, "This calls for the mighty switch statement", yet the code is if / elseif ;-)
:D goodness - that's bad. Thanks for the catch.
The $feed_element part would be an array if we are dealing with a feed tag that occurs more than once in a row - e. g. tags.
If you want to know more about how this works, look here: http://cvs.drupal.org/viewvc.py/drupal/contributions/modules/feedapi_map...
Alex
an image mapper
I can't wait till rich media is automatically mapped. I built a website for my school here: http://ebiz2.byu.edu/analytics that imports personal blogs of students. One of the first complaints I heard was why there pictures aren't being imported.
Hi Kyle You can make
Hi Kyle
You can make pictures and media in aggregated content show up by making sure to use the right input filter configuration and that the relevant tags are allowed (settings/feedapi).
However, I guess you rather refer yourself to actually downloading content embedded in text or in enclosures.
Indeed, a mapper for such functionality would be an exciting thing.
You could start downloading whatever media you can find on an RSS feed: music, image, sound.
Is this the CCK field that we would like to map to for downloading embedded media:
http://drupal.org/project/emfield
?
Alex
whoops!
I was wrong. Thanks for reminding me about needing to change the input filters. I'd forgotten completely about that. I added![]()