/[drupal]/contributions/sandbox/alex_b/feedapi_mapper/feedapi_mapper.module
ViewVC logotype

Contents of /contributions/sandbox/alex_b/feedapi_mapper/feedapi_mapper.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.24 - (show annotations) (download) (as text)
Fri Jan 18 21:45:09 2008 UTC (22 months, 1 week ago) by alexb
Branch: MAIN
CVS Tags: HEAD
Changes since 1.23: +2 -2 lines
File MIME type: text/x-php
Add version strings, improve some verbiage.
1 <?php
2 // $Id: feedapi_mapper.module,v 1.23 2007/12/10 20:41:21 alexb Exp $
3
4 /**
5 * Implementation of hook_help().
6 */
7 function feedapi_mapper_help($section) {
8 switch ($section) {
9 case 'admin/help#feedapi_mapper':
10 return t('FeedAPI Mapper - maps feed item elements to node fields');
11 case 'feedapi_mapper/full_name':
12 return t('FeedAPI Mapper - maps feed item elements to node fields');
13 }
14 }
15
16 /**
17 * Implementation of hook_menu().
18 */
19 function feedapi_mapper_menu($may_cache) {
20 $items = array();
21 if (!$may_cache) {
22 if (arg(0) == 'node' && is_numeric(arg(1))) {
23 $node = node_load(arg(1));
24 if (feedapi_enabled($node->type, 'feedapi_node')) {
25 global $user;
26 $own_feed = (($node->uid == $user->uid) && user_access('edit own '. $node->type . ' content'));
27 $items[] = array('path' => 'node/'. $node->nid .'/map',
28 'title' => t('Map'),
29 'callback' => 'feedapi_mapper_page',
30 'callback arguments' => array($node),
31 'type' => MENU_LOCAL_TASK,
32 'access' => (user_access('administer feedapi') || $own_feed),
33 );
34 }
35 }
36 else if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'types') {
37 $node_type = arg(3) ? str_replace('-', '_', arg(3)) : NULL;
38 if ($node_type && feedapi_enabled($node_type, 'feedapi_node')) {
39 $items[] = array(
40 'path' => 'admin/content/types/'. str_replace('_', '-', $node_type) .'/edit',
41 'title' => t('Edit'),
42 'callback' => 'drupal_get_form',
43 'callback arguments' => array('node_type_form', $node_type),
44 'type' => MENU_DEFAULT_LOCAL_TASK,
45 );
46 // Todo: this should finally live in a sub tab of "Feed".
47 $node = new stdClass();
48 $node->type = $node_type;
49 $items[] = array('path' => 'admin/content/types/'. str_replace('_', '-', $node_type) .'/map',
50 'title' => t('Map'),
51 'callback' => 'feedapi_mapper_page',
52 'callback arguments' => array($node),
53 'type' => MENU_LOCAL_TASK,
54 'access' => user_access('administer feedapi'),
55 );
56 }
57 }
58 }
59 return $items;
60 }
61
62 /**
63 * Implementation of hook_nodeapi().
64 */
65 function feedapi_mapper_nodeapi(&$node, $op, $teaser, $page) {
66 switch ($op) {
67 case 'prepare':
68 if ($node->feedapi->feed_item) {
69 _feedapi_mapper_map($node);
70 }
71 break;
72 }
73 }
74
75 /**
76 * Map feed item elements to node.
77 */
78 function _feedapi_mapper_map(&$node) {
79 // Load mapping stored for this node.
80 if (!$mapping = _feedapi_mapper_load_mapping($node->feedapi->feed_nid)) {
81 $feed_node = node_load($node->feedapi->feed_nid);
82 $mapping = _feedapi_mapper_load_mapping($feed_node->type);
83 }
84 if ($mapping) {
85 // Load available mappers.
86 _feedapi_mapper_load_mappers();
87 // Convert item to array:
88 $feed_item = _feedapi_mapper_obj2array($node->feedapi->feed_item);
89 foreach ($mapping as $element_path => $field) {
90 if ($field) { // Double check if field is set - todo: this step could be skipped.
91 $field = unserialize($field);
92 $element_path = unserialize($element_path);
93 // Get the feed item element on $element_path and pass it into the mapping function.
94 $feed_item_element = _feedapi_mapper_get_feed_item_element($element_path, $feed_item);
95 $node = call_user_func($field[0] .'_feedapi_mapper', 'map', $node, $field[1], $feed_item_element, $field[2]);
96 }
97 }
98 }
99 }
100
101 /**
102 * Returns feed item element on given path.
103 */
104 function _feedapi_mapper_get_feed_item_element($path, $item) {
105 $p = array_shift($path);
106 if (count($path) > 0) {
107 return _feedapi_mapper_get_feed_item_element($path, $item[$p]);
108 }
109 else {
110 return $item[$p];
111 }
112 }
113
114 /**
115 * Callback function for hook_menu().
116 */
117 function feedapi_mapper_page($node) {
118 $names = node_get_types('names');
119 drupal_set_title($node->title ? $node->title : $names[$node->type]);
120 $output .= t('Map feed item elements to feed item node fields.');
121 $output .= drupal_get_form('feedapi_mapper_form', $node);
122 return $output;
123 }
124
125 /**
126 * Mapping form.
127 */
128 function feedapi_mapper_form($node) {
129 // Get fields of node type with available feed element mappers.
130 if ($node->feed->settings['processors']['feedapi_node']['content_type']) {
131 $feed_item_type = $node->feed->settings['processors']['feedapi_node']['content_type'];
132 }
133 else {
134 $node_type_settings = _feedapi_get_settings(array('node_type' => $node->type));
135 $feed_item_type = $node_type_settings['processors']['feedapi_node']['content_type'];
136 }
137 $field_mappers = _feedapi_mapper_get_field_mappers($feed_item_type);
138
139 // Get elements of feed items.
140 if ($merged_item = _feedapi_mapper_get_items_merged($node)) {
141 $elements = _feedapi_mapper_get_feed_elements($merged_item);
142 }
143 else {
144 $elements = _feedapi_mapper_get_standard_elements();
145 }
146 // Load stored mapping.
147 if (!$mapping = _feedapi_mapper_load_mapping($node->nid )) {
148 $mapping = _feedapi_mapper_load_mapping($node->type);
149 }
150
151 if ($merged_item) {
152 $form['feed_item'] = array(
153 '#type' => 'fieldset',
154 '#title' => t('Feed item example'),
155 '#collapsible' => TRUE,
156 '#collapsed' => TRUE,
157 '#description' => t('All feed items of this feed merged into one. Here you can see which feed item elements are available for mapping. As this view is derived from the actual feed items on this feed, there might be more mappable elements than those listed here.'),
158 );
159 $form['feed_item']['item'] = array(
160 '#type' => 'markup',
161 '#value' => '<pre>'. print_r(_feedapi_mapper_truncate_array($merged_item), true) .'</pre>',
162 );
163 }
164 $form['nid'] = array(
165 '#type' => 'value',
166 '#value' => ($node->nid ? $node->nid: $node->type),
167 );
168 // Pass on feed item elements.
169 $form['elements'] = array('#type' => 'value', '#value' => $elements);
170 // Print descriptions if there are any.
171 if ($descriptions = theme('feedapi_mapper_descriptions', _feedapi_mapper_get_field_mappers_descriptions($feed_item_type))) {
172 $form['descriptions'] = array(
173 '#type' => 'fieldset',
174 '#title' => t('Description of available mappers'),
175 '#description' => t('Only mappers for existing fields on feed items show up.'),
176 '#collapsible' => TRUE,
177 '#collapsed' => FALSE,
178 '#tree' => TRUE,
179 );
180 $form['descriptions']['descriptions'] = array(
181 '#type' => 'markup',
182 '#value' => $descriptions,
183 );
184 }
185 // Create mapping form.
186 $form['mapping'] = array(
187 '#type' => 'fieldset',
188 '#title' => t('Edit mapping'),
189 '#description' => t('This is a list of feed item elements that are available for mapping. Choose a mapping from the drop down to map a feed item element to a node field.'),
190 '#collapsible' => TRUE,
191 '#collapsed' => FALSE,
192 '#tree' => TRUE,
193 );
194 foreach ($elements as $element_name => $path) {
195 $form['mapping'][$element_name] = array(
196 '#type' => 'select',
197 '#title' => $element_name,
198 '#options' => $field_mappers,
199 '#default_value' => $mapping[$path],
200 );
201 }
202 $form['submit'] = array(
203 '#type' => 'submit',
204 '#value' => t('Update'),
205 );
206 return $form;
207 }
208
209 /**
210 * Submit hook.
211 */
212 function feedapi_mapper_form_submit($form_id, $form_values) {
213 _feedapi_mapper_store_mapping($form_values['nid'], $form_values['mapping'], $form_values['elements']);
214 }
215
216 /**
217 * Store mapping.
218 * @param $param
219 * node id or node type.
220 * @param $mapping
221 * Mapping.
222 * @param $elements
223 * All elements of feed item.
224 */
225 function _feedapi_mapper_store_mapping($param, $mapping, $elements) {
226 // Wrap the mapping array (element_name => node_field)
227 // and the elements array (element_name => element_path)
228 // arrays into one:
229 // We store a mapping as element_path => node_field.
230 // - element_path and node_field are both a serialized array.
231 $stored_mapping = array();
232 foreach ($mapping as $element_name => $field) {
233 if ($field) {
234 $stored_mapping[$elements[$element_name]] = $field;
235 }
236 }
237 if (is_numeric($param)) {
238 if (db_result(db_query('SELECT * FROM {feedapi_mapper} WHERE nid = %d', $param))) {
239 db_query('UPDATE {feedapi_mapper} SET mapping = "%s" WHERE nid = %d', serialize($stored_mapping), $param);
240 }
241 else {
242 db_query('INSERT INTO {feedapi_mapper} (nid, mapping) VALUES (%d, "%s")', $param, serialize($stored_mapping));
243 }
244 }
245 else if (is_string($param)) {
246 variable_set('feedapi_mapper_mapping_'. $param, $stored_mapping);
247 }
248 }
249
250 /**
251 * Retrieve mapping from db.
252 * @param $param
253 * node id or node type
254 * @return Associative array in the format
255 * array(
256 * element_path1 => node_field1,
257 * element_path2 => node_field2,
258 * );
259 * where element_pathx and node_fieldx are both serialized arrays.
260 */
261 function _feedapi_mapper_load_mapping($param) {
262 static $mappings;
263 if (is_numeric($param)) {
264 if (isset($mappings[$param])) {
265 return $mappings[$param];
266 }
267 if ($mapping = db_result(db_query('SELECT mapping FROM {feedapi_mapper} WHERE nid = %d', $param))) {
268 $mappings[$param] = unserialize($mapping);
269 return $mappings[$param];
270 }
271 }
272 else if (is_string($param)) {
273 return variable_get('feedapi_mapper_mapping_'. $param, array());
274 }
275 }
276
277 /**
278 * Load mapper implementations.
279 */
280 function _feedapi_mapper_load_mappers() {
281 static $loaded = FALSE;
282 if (!$loaded) {
283 // Load all feedapi mapper implementations from ./mappers
284 $path = drupal_get_path('module', 'feedapi_mapper') . '/mappers';
285 $files = drupal_system_listing('.*\.inc$', $path, 'name', 0);
286 foreach($files as $file) {
287 require_once("./$file->filename");
288 }
289 // Rebuild cache.
290 module_implements('', FALSE, TRUE);
291 }
292 $loaded = TRUE;
293 }
294
295 /**
296 * Todo: move this guy to feedapi.
297 */
298 function feedapi_parse($feed) {
299 return _feedapi_call_parsers($feed, $feed->parsers, $feed->half_done ? FALSE : TRUE);
300 }
301
302 /**
303 * Returns all feed items on node as one merged item.
304 */
305 function _feedapi_mapper_get_items_merged($node) {
306 if ($node->feed) {
307 $feed = feedapi_parse($node->feed);
308 // Convert items to array.
309 $items = _feedapi_mapper_obj2array($feed->items);
310 // Merge items to one item.
311 if (is_array($items)) {
312 foreach ($items as $item) {
313 $merged = _feedapi_mapper_array_merge_recursive($item, $merged);
314 }
315 }
316 return $merged;
317 }
318 }
319
320 /**
321 * Sister function of _feedapi_mapper_get_feed_elements().
322 * Returns array in same format. Only difference: does not take
323 * a real feed that it analyzes, but returns some standard elements.
324 * Todo: probably create hook for extending modules.
325 * @return: Array in the format array('pathname' => 'serializedpath')
326 */
327 function _feedapi_mapper_get_standard_elements() {
328 $paths[] = array('options', 'original_author', 'name');
329 $paths[] = array('options', 'original_author');
330 $paths[] = array('options', 'tags');
331 foreach ($paths as $path) {
332 $elements[implode('->', $path)] = serialize($path);
333 }
334 return $elements;
335 }
336
337 /**
338 * Takes a feed item and retrieves paths to all elements.
339 * Use a merged feed item (_feedapi_mapper_get_items_merged()) for best results.
340 * @return: Array in the format array('pathname' => 'serializedpath')
341 */
342 function _feedapi_mapper_get_feed_elements($merged_item) {
343 // Retrieve paths to elements in merged item.
344 // Stick them into an array where the key is the serialized path and the value is the element name.
345 $elements = array();
346 while (count($merged_item)) {
347 $path = array();
348 $path = _feedapi_mapper_next_element_path($merged_item, $path);
349 $elements[implode('->', $path)] = serialize($path);
350 }
351 return $elements;
352 }
353
354 /**
355 * Traverse an associative array and look for path to first leaf.
356 * If found, unset leaf and return path to it.
357 * @return
358 * A path to a leaf element in the format
359 * array(path, to, leaf, element);
360 */
361 function _feedapi_mapper_next_element_path(&$items, &$path) {
362 // This recursion is a bit shaky. Put on breaks.
363 static $i;
364 $i++;
365 if ($i > 200) {
366 drupal_set_message(t('Error in recursion _feedapi_mapper_next_element_path()'), 'error');
367 return array();
368 }
369 foreach ($items as $key => $value) {
370 $path[] = $key;
371 // Recurse if value is array and if it contains elements.
372 if (is_array($items[$key]) && count($items[$key])) {
373 // Arrays with keys of 0 are not considered collections of same items - reached a leaf.
374 if (isset($items[$key][0])) {
375 unset($items[$key]);
376 return $path;
377 }
378 else if ($result_path = _feedapi_mapper_next_element_path($items[$key], $path)) {
379 // Leaf was found, pass it up.
380 return $result_path;
381 }
382 }
383 else {
384 // Reached leaf, unset it and return path to it.
385 unset($items[$key]);
386 return $path;
387 }
388 }
389 // No leaves found in this pass.
390 return FALSE;
391 }
392
393 /**
394 * Converts a multidemensional associative array with interdispersed objects into
395 * an associative array only.
396 */
397 function _feedapi_mapper_obj2array($items) {
398 if (is_object($items)) {
399 $items = (array) $items;
400 }
401 if (is_array($items)) {
402 foreach ($items as $key => $value) {
403 $items[$key] = _feedapi_mapper_obj2array($value);
404 }
405 }
406 return $items;
407 }
408
409 /**
410 * Truncates all strings in cascaded array.
411 */
412 function _feedapi_mapper_truncate_array($array) {
413 foreach ($array as $k => $v) {
414 if (is_string($v)) {
415 $array[$k] = strip_tags($v);
416 $array[$k] = truncate_utf8($array[$k], 200, TRUE, TRUE);
417 }
418 else if (is_array($v)) {
419 $array[$k] = _feedapi_mapper_truncate_array($v);
420 }
421 }
422 return $array;
423 }
424
425 /**
426 * Like array_merge_recursive. Only difference: does NOT merge
427 * two different keys into an array, but merges key on key.
428 * Argument 1 always has to be an array.
429 */
430 function _feedapi_mapper_array_merge_recursive($array1, $array2) {
431 foreach ($array1 as $k => $v) {
432 if (is_array($array1[$k])) {
433 $result[$k] = _feedapi_mapper_array_merge_recursive($array1[$k], $array2[$k]);
434 }
435 else {
436 $result[$k] = $array1[$k];
437 }
438 }
439 if (is_array($array2)) {
440 foreach ($array2 as $k => $v) {
441 if (is_array($array2[$k])) {
442 $result[$k] = _feedapi_mapper_array_merge_recursive($array2[$k], $array1[$k]);
443 }
444 else {
445 $result[$k] = $array2[$k];
446 }
447 }
448 }
449 return $result;
450 }
451
452 /**
453 * Get field mappers for a given node type. Returns an array of all
454 * feed mappers that are applicable to this node type.
455 * @param $node_type
456 * Valid node type.
457 * @return
458 * Array of fields that are mappable for this content type.
459 */
460 function _feedapi_mapper_get_field_mappers($node_type) {
461 // Not sure wether to take fields from node or from form...
462 // Pro form: there might be elements on form that you can't see on node (?).
463 $form = _feedapi_mapper_get_node_form($node_type);
464 $node->type = $node_type;
465 $field_mappers[0] = t('No mapping');
466 // Load all available mappers and create an array of fields available as mapping target.
467 _feedapi_mapper_load_mappers();
468 $modules = module_implements('feedapi_mapper');
469 foreach ($form as $field_name => $field) {
470 foreach ($modules as $module) {
471 if ($sub_fields = module_invoke($module, 'feedapi_mapper', 'list', $node, $field_name)) {
472 $field_category = t('Map to') .' '. $field_name .' '. t('(!module_name module)', array('!module_name' => $module));
473 if (is_array($sub_fields)) {
474 $field_mappers[$field_category] = array();
475 foreach ($sub_fields as $sub_field_id => $sub_field_name) {
476 $field_mappers[$field_category][serialize(array($module, $field_name, $sub_field_id))] = $sub_field_name;
477 }
478 }
479 else {
480 $field_mappers[serialize(array($module, $field_name))] = $field_category;
481 }
482 }
483 }
484 }
485 return $field_mappers;
486 }
487
488 /**
489 * Returns descriptions for all mappable fields of given node type.
490 * @return
491 * Array in the format
492 * array('field_name_a' =>
493 * array('module_name_a' => 'Descriptive text'),
494 * 'module_name_b' => ...),
495 * 'field_name_b' => array(...),
496 * );
497 */
498 function _feedapi_mapper_get_field_mappers_descriptions($node_type) {
499 // Not sure wether to take fields from node or from form...
500 // Pro form: there might be elements on form that you can't see on node (?).
501 $form = _feedapi_mapper_get_node_form($node_type);
502 $node->type = $node_type;
503 // Load all available mappers and create an array of fields available as mapping target.
504 _feedapi_mapper_load_mappers();
505 $modules = module_implements('feedapi_mapper');
506 $descriptions = array();
507 foreach ($form as $field_name => $field) {
508 foreach ($modules as $module) {
509 if ($description = module_invoke($module, 'feedapi_mapper', 'describe', $node, $field_name)) {
510 $descriptions[$field_name][$module] = $description;
511 }
512 }
513 }
514 return $descriptions;
515 }
516
517 function _feedapi_mapper_get_node_form($node_type) {
518 $form_id = $node_type .'_node_form';
519 $node = new stdClass();
520 $node->type = $node_type;
521 // Borrow this from CCK:
522 // Some modules (userreview...) "hide" their node forms, resulting in no field
523 // being listed. We set a special flag to inform them this form is special.
524 $node->cck_dummy_node_form = TRUE;
525
526 $form = call_user_func_array('drupal_retrieve_form', array($form_id, $node));
527 drupal_process_form($form_id, $form);
528 // return drupal_render_form($args[0], $form);
529 return $form;
530 }
531
532 /**
533 * Theming function for outputting result of _feedapi_mapper_get_field_mappers_descriptions().
534 * @param $descriptions
535 * Result of _feedapi_mapper_get_field_mappers_descriptions().
536 * @return
537 * HTML output.
538 */
539 function theme_feedapi_mapper_descriptions($descriptions) {
540 $output .= '<dl>';
541 foreach ($descriptions as $field_mapper => $implementations) {
542 foreach ($implementations as $module => $description) {
543 $output .= '<dt><strong>'. $field_mapper .' '. t('(!module_name module)', array('!module_name' => $module)) .'</strong></dt>';
544 $output .= '<dd>'. $description .'</dd>';
545 }
546 }
547 $output .= '</dl>';
548 return $output;
549 }

  ViewVC Help
Powered by ViewVC 1.1.2