/[drupal]/contributions/modules/fb/fb_feed.module
ViewVC logotype

Contents of /contributions/modules/fb/fb_feed.module

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


Revision 1.20 - (show annotations) (download) (as text)
Tue Oct 27 20:39:52 2009 UTC (4 weeks ago) by yogadex
Branch: MAIN
Changes since 1.19: +3 -3 lines
File MIME type: text/x-php
avoid use of dprint_r()
1 <?php
2
3 /**
4 * @file
5 *
6 * Helpers for Facebook feeds
7 * (http://wiki.developers.facebook.com/index.php/New_Design_Feed_Wall)
8 */
9
10 // Facebook used to support multiple messages per bundle, now only one.
11 define('FB_FEED_LINES_PER_BUNDLE', 1);
12 define('FB_FEED_SHORTS_PER_BUNDLE', 1);
13 define('FB_FEED_ACTION_LINKS_PER_BUNDLE', 3);
14
15 define('FB_FEED_NODE_TYPE_TEMPLATE', 'fb_feed_template');
16
17 define('FB_FEED_HOOK', 'fb_feed');
18 define('FB_FEED_OP_TOKEN_ALTER', 'fb_feed_token_alter');
19
20 function fb_feed_init() {
21 drupal_add_js(drupal_get_path('module', 'fb_feed') . '/fb_feed.js');
22 }
23
24 function fb_feed_menu() {
25 $items = array();
26 $items['fb_feed/register'] = array(
27 'title' => 'Register template',
28 'type' => MENU_CALLBACK,
29 'access arguments' => array('adminster fb apps'),
30 'page callback' => 'fb_feed_register_cb',
31 );
32 $items['fb_feed/deactivate'] = array(
33 'title' => 'Deactivate template',
34 'type' => MENU_CALLBACK,
35 'access arguments' => array('adminster fb apps'),
36 'page callback' => 'drupal_get_form',
37 'page arguments' => array('fb_feed_deactivate_confirm'),
38 );
39
40 $items['node/%fb_app_nid/fb_feed/templates'] = array(
41 'title' => t('Feed Templates'),
42 'type' => MENU_LOCAL_TASK,
43 'access callback' => 'node_access',
44 'access arguments' => array('update', 1),
45 'page callback' => 'fb_feed_template_page',
46 'page arguments' => array(1),
47 );
48
49 return $items;
50 }
51
52 function fb_feed_deactivate_confirm($fb_app_nid, $bundle_id) {
53 $result = db_query("SELECT n.nid, n.title FROM {node} n LEFT JOIN {fb_feed_template} fft ON fft.nid = n.nid WHERE fft.fb_app_nid = %d AND fft.bundle_id = %f",
54 $fb_app_nid, $bundle_id);
55 $data = db_fetch_object($result);
56 if ($data->nid) {
57 $form['nid'] = array('#type' => 'hidden', '#value' => $data->nid);
58 $description = t('This will deactivate the template on Facebook. The template node will not be deleted.');
59 $question = t('Really deactivate the !template_link template?',
60 array('!template_link' => l($data->title, 'node/'.$data->nid)));
61 }
62 else
63 $question = t('Really deactivate template bundle %bundle_id?',
64 array('%bundle_id' => $bundle_id));
65
66 $form['bundle_id'] = array('#type' => 'hidden',
67 '#value' => $bundle_id);
68 $form['fb_app_nid'] = array('#type' => 'hidden',
69 '#value' => $fb_app_nid);
70
71 return confirm_form($form, $question, 'node/'.$fb_app_nid.'/fb_feed/templates',
72 $description);
73 }
74
75 function fb_feed_deactivate_confirm_submit($form_id, $values) {
76 $success = fb_feed_deactivate_template($values['fb_app_nid'], $values['bundle_id']);
77 if ($success && $values['nid'])
78 db_query("UPDATE {fb_feed_template} SET bundle_id=0 WHERE nid=%d",
79 $values['nid']);
80
81 if ($success) {
82 drupal_set_message(t('The template %bundle_id has been deactivated.',
83 array('%bundle_id' => $values['bundle_id'])));
84 }
85 else {
86 drupal_set_message(t('The template %bundle_id could not be deactivated.',
87 array('%bundle_id' => $values['bundle_id'])), 'error');
88 }
89 return 'node/'.$values['fb_app_nid'].'/fb_feed/templates';
90 }
91
92 function fb_feed_deactivate_template($fb_app_nid, $bundle_id) {
93 $fb_app = fb_get_app(array('nid' => $fb_app_nid));
94 $fb = fb_api_init($fb_app, FB_FBU_ANY);
95 if (!$fb) {
96 drupal_set_message(t('Facebook API failed to connect. Template could not be deactivated.', 'error'));
97 return FALSE;
98 }
99 else {
100 $result = $fb->api_client->feed_deactivateTemplateBundleByID($bundle_id);
101 fb_report_errors($fb, 'Error while deactivating template.');
102 }
103 return $result;
104 }
105
106 function fb_feed_register_cb($nid) {
107 $node = node_load($nid);
108 $bundle_id = fb_feed_register_template($node);
109 drupal_goto('node/'.$node->fb_app_nid.'/fb_feed/templates');
110 }
111
112 function fb_feed_template_page($node) {
113 $fb_app = $node->fb_app;
114 $registered = array();
115 $unregistered = array();
116 // Query all known templates
117 $result = db_query("SELECT n.nid, n.title, n.status, fft.bundle_id FROM {node} n LEFT JOIN {fb_feed_template} fft ON fft.nid = n.nid WHERE fft.nid IS NOT NULL");
118 while ($data = db_fetch_object($result)) {
119 if ($data->bundle_id) {
120 $registered[$data->bundle_id] = $data;
121 }
122 else {
123 $unregistered[] = $data;
124 }
125 }
126
127 // Query facebook for a list of registered templates we know nothing about.
128 $fb = fb_api_init($fb_app, FB_FBU_ANY);
129 if ($fb) {
130 try {
131 $all = $fb->api_client->feed_getRegisteredTemplateBundles();
132
133 if (is_array($all))
134 foreach ($all as $data) {
135 if (isset($registered[$data['template_bundle_id']])) {
136 $registered[$data['template_bundle_id']]->confirmed = $data;
137 }
138 else
139 $registered[$data['template_bundle_id']] = $data;
140 }
141 }
142 catch (Exception $e) {
143 //dpm($fb, "fb");
144 //dpm($fb->api_client, "api_client");
145 fb_log_exception($e, t('Failed to get template bundles.'));
146 drupal_set_message(t('This page may show more information if you configure an infinite session or Facebook Connect.'));
147 }
148 }
149 else {
150 drupal_set_message("Facebook API not available. Some options will not be shown. Possible infinite session configuration problem.", 'error');
151 }
152
153 // Display one table of registered templates and another of unregistered.
154 if (count($unregistered)) {
155 $rows = array();
156 foreach ($unregistered as $data) {
157 $ops = array(l(t('register'), 'fb_feed/register/'.$data->nid));
158 $row = array(l($data->title, 'node/'.$data->nid),
159 implode('&nbsp;|&nbsp;', $ops));
160 $rows[] = $row;
161 }
162 $header = array(t('Unregistered Template'), t('Operations'));
163 $output .= theme('table', $header, $rows);
164 }
165 else {
166 $output .= '<p>' . t('No unregistered templates found.') . '</p>';
167 }
168 if (count($registered)) {
169 $rows = array();
170 foreach ($registered as $data) {
171 $ops = array();
172
173 if ($data->confirmed)
174 $ops[] = l(t('deactivate'), 'fb_feed/deactivate/'.$fb_app->nid.'/'.$data->bundle_id);
175 else if ($data->nid && $fb && is_array($all))
176 $ops = array(l(t('re-register'), 'fb_feed/register/'.$data->nid));
177 else if ($data->template_bundle_id)
178 $ops[] = l(t('deactivate'), 'fb_feed/deactivate/'.$fb_app->nid.'/'.$data['template_bundle_id']);
179
180
181
182 if ($data->nid)
183 $title = l($data->title, 'node/'.$data->nid);
184 else
185 // Use a template learned from facebook.
186 $title = $data['one_line_story_templates'][0];
187 if ($data->bundle_id)
188 $bundle_id = $data->bundle_id;
189 else
190 $bundle_id = $data['template_bundle_id'];
191 $row = array($title,
192 $bundle_id,
193 implode('&nbsp;|&nbsp;', $ops),
194 );
195 $rows[] = $row;
196 }
197 $header = array(t('Registered Template'), t('Bundle ID'), t('Operations'));
198 $output .= theme('table', $header, $rows);
199 }
200 else {
201 $output .= '<p>' . t('No registered templates found.') . '</p>';
202 }
203
204
205 return $output;
206 }
207
208 /**
209 * hook_node_info.
210 */
211 function fb_feed_node_info() {
212 return array(FB_FEED_NODE_TYPE_TEMPLATE =>
213 array('name' => t('Facebook Template Bundle'),
214 'module' => 'fb_feed',
215 'description' => t('Templates for posting information to Facebook feeds.'),
216 'help' => t('Configure the feed stories of your Facebook Application.'),
217 ),
218 );
219 }
220
221 /**
222 * Use same permissions as fb_app module.
223 */
224 function fb_feed_access($op, $node) {
225 if (user_access('administer fb apps'))
226 return TRUE;
227 if ($op == 'create' && user_access('create fb apps'))
228 return TRUE;
229 else if ($op == 'update' || $op == 'delete') {
230 if ($node->uid == $user->uid &&
231 user_access('edit own fb apps'))
232 return TRUE;
233 }
234 }
235
236 function fb_feed_form(&$node, &$param) {
237 $form = array();
238 $type = node_get_types('type', $node);
239 // We need to define form elements for the node's title and body.
240 $form['title'] = array('#type' => 'textfield',
241 '#title' => check_plain($type->title_label),
242 '#required' => TRUE,
243 '#default_value' => $node->title,
244 '#weight' => -5,
245 '#description' => t('Identifies the template bundle to site administrators.'),
246 );
247
248 $form['label'] = array('#type' => 'textfield',
249 '#title' => t('Label'),
250 '#required' => TRUE,
251 '#default_value' => $node->label,
252 '#description' => t('Used to refer to this template programmatically. Use only alphanumeric characters.<br/>When hosting an application on both dev and production servers, use the same labels. Bundle ids, apikeys and so on must change between servers, so custom code must rely on this label. On any single server, each label must be unique.'),
253 '#weight' => -5,
254 );
255
256 if ($type->body_label) {
257 $form['body_filter']['body'] =
258 array('#type' => 'textarea',
259 '#title' => check_plain($type->body_label),
260 '#default_value' => $node->body,
261 '#required' => FALSE,
262 '#description' => 'Not sure yet how this will be used.',
263 );
264 $form['body_filter']['filter'] = filter_form($node->format);
265 }
266
267 // Now we define the form elements specific to our node type.
268
269 $options = fb_get_app_options(FALSE);
270 $form['fb_app_nid'] =
271 array('#type' => 'select',
272 '#title' => t('Application'),
273 '#default_value' => $values['fb_app_nid'],
274 '#options' => $options,
275 '#description' => t('Which application will use these templates?<br />'),
276 '#weight' => -5,
277 '#required' => TRUE,
278 );
279
280 $form['fb_feed_data'] = array('#tree' => TRUE,
281 '#weight' => -4,
282 );
283 $form['fb_feed_data']['description'] =
284 array('#type' => 'markup',
285 '#value' => t('Read <a target=_blank href="!url">about template bundles</a> for more information.',
286 array('!url' => 'http://wiki.developers.facebook.com/index.php/Feed.registerTemplateBundle')),
287 );
288
289 $form['fb_feed_data']['example'] =
290 array('#type' => 'markup',
291 '#value' => t('Here\'s an example that produces a message like "<em>User Name</em> wrote about <em>something</em> on <em>application</em>," where <em>something</em> is the title of a node:<br/><em>{*actor*} wrote about {*fb-feed-canvas-node-link*} on {*fb-feed-canvas-app-link*}</em>'),
292 '#prefix' => '<p>',
293 '#suffix' => '</p>',
294 );
295
296 $form['fb_feed_data']['line'] = array('#type' => 'fieldset',
297 '#title' => t('One-line templates'),
298 '#description' => t('Note that one-line templates must begin with the {*actor*} token.'),
299 '#collapsible' => TRUE,
300 );
301 $i = 0;
302 while ($i < FB_FEED_LINES_PER_BUNDLE) {
303 $form['fb_feed_data']['line'][$i] =
304 array('#type' => 'textfield',
305 '#title' => t('Additional one-line template'),
306 '#default_value' => $node->fb_feed_data['line'][$i],
307 );
308 $i++;
309 }
310 $form['fb_feed_data']['line'][0]['#title'] = t('One-line template');
311 $form['fb_feed_data']['line'][0]['#required'] = TRUE;
312
313
314
315
316 $form['fb_feed_data']['short'] = array('#type' => 'fieldset',
317 '#title' => t('Short templates'),
318 '#description' => t('Note that titles must begin with the {*actor*} token.'),
319 '#collapsible' => TRUE,
320 );
321
322 $i = 0;
323 while ($i < FB_FEED_SHORTS_PER_BUNDLE) {
324 $form['fb_feed_data']['short'][$i]['template_title'] =
325 array('#type' => 'textfield',
326 '#title' => t('Additional short template title'),
327 '#default_value' => $node->fb_feed_data['short'][$i]['template_title'],
328 );
329 $form['fb_feed_data']['short'][$i]['template_body'] =
330 array('#type' => 'textarea',
331 '#title' => t('Additional short template body'),
332 '#default_value' => $node->fb_feed_data['short'][$i]['template_body'],
333 '#rows' => 2,
334 );
335 $i++;
336 }
337 $form['fb_feed_data']['short'][0]['template_title']['#title'] = t('Short template title');
338 $form['fb_feed_data']['short'][0]['template_body']['#title'] = t('Short template body');
339
340
341 $i = 0;
342 while ($i < FB_FEED_ACTION_LINKS_PER_BUNDLE) {
343 $form['fb_feed_data']['action_links'][$i] = array(
344 '#type' => 'fieldset',
345 '#title' => t('Additional Action Link'),
346 '#description' => t('For additional information, see <a target=_blank href="!href">Action Links</a>.',
347 array('!href' => 'http://wiki.developers.facebook.com/index.php/Action_Links')),
348 '#collapsible' => TRUE,
349 '#collapsed' => $node->fb_feed_data['action_links'][$i]['text'] ? FALSE : TRUE,
350 );
351 $form['fb_feed_data']['action_links'][$i]['text'] = array(
352 '#type' => 'textfield',
353 '#title' => t('Text'),
354 '#default_value' => $node->fb_feed_data['action_links'][$i]['text'],
355 );
356 $form['fb_feed_data']['action_links'][$i]['href'] = array(
357 '#type' => 'textfield',
358 '#title' => t('Href'),
359 '#default_value' => $node->fb_feed_data['action_links'][$i]['href'],
360 );
361 $i++;
362 }
363 $form['fb_feed_data']['action_links'][0]['#title'] = t('First Action Link');
364 $form['fb_feed_data']['action_links'][0]['#collapsed'] = FALSE;
365
366 // TODO: add action links http://wiki.developers.facebook.com/index.php/Feed.registerTemplateBundle
367
368 if (module_exists('token')) {
369 $form['token_help'] = array('#type' => 'fieldset',
370 '#collapsible' => TRUE,
371 '#collapsed' => FALSE,
372 '#title' => t("Token help"),
373 '#description' => t('Facebook uses the special token <em>{*actor*}</em> for the user performing an action. Another special token is <em>{*target*}</em>, which should only be used by expert users, as Facebook often fails to publish messages with the <em>{*target*}</em> token.'),
374 );
375 $form['token_help']['warning'] = array(
376 '#type' => 'markup',
377 '#value' => t('<strong>Bugs in FBJS may prevent token substitution from working on canvas pages.</strong> Facebook Connect pages are OK, but problems have been reported on FBML canvas pages when publishing messages with token substitution.'),
378 '#prefix' => '<p>',
379 '#suffix' => '</p>',
380 );
381 foreach (array('node', 'comment', 'user', 'fb_app') as $type) {
382 $form['token_help'][$type] =
383 array('#type' => 'fieldset',
384 '#collapsible' => TRUE,
385 '#collapsed' => TRUE,
386 '#title' => t("!type tokens", array('!type' => $type)),
387 '#description' => theme('token_help', $type, "{*", "*}"),
388 );
389 }
390 }
391
392
393 return $form;
394 }
395
396 function fb_feed_validate($node) {
397 // TODO
398 // Ensure each label is unique.
399 }
400
401 function fb_feed_register_template($node) {
402
403 $fb_app = fb_get_app(array('nid' => $node->fb_app_nid));
404 // Login to facebook as either the current user or the infinite session
405 if ($fb_app) {
406 $fb = fb_api_init($fb_app, FB_FBU_ANY);
407 if ($fb) {
408 // Clean up the data before sending to facebook. Send no empty templates.
409 $lines = array();
410 foreach ($node->fb_feed_data['line'] as $line) {
411 if ($line)
412 $lines[] = $line;
413 }
414 $shorts = array();
415 foreach ($node->fb_feed_data['short'] as $item) {
416 if ($item['template_title'])
417 $shorts[] = $item;
418 }
419 $full = array();
420 if ($node->fb_feed_data['full']['template_title'])
421 $full = $node->fb_feed_data['full'];
422
423 $action_links = $node->fb_feed_data['action_links'];
424 foreach ($action_links as $i => $data) {
425 if (!$data['text'] || !$data['href']) {
426 unset($action_links[$i]);
427 }
428 }
429
430 if ($fb) {
431 try {
432 $bundle_id =
433 $fb->api_client->feed_registerTemplateBundle($lines, $shorts, $full, $action_links);
434 }
435 catch (Exception $e) {
436 fb_log_exception($e, t('Error attempting to register template bundle.'), $fb);
437 }
438 if ($bundle_id)
439 drupal_set_message(t('Registered template bundle %bundle_id with Facebook.',
440 array('%bundle_id' => $bundle_id)));
441 else
442 drupal_set_message(t('Failed to register the template.'), 'error');
443
444 if ($bundle_id && $node->nid)
445 db_query("UPDATE {fb_feed_template} fft SET bundle_id=%f WHERE nid=%d",
446 $bundle_id, $node->nid);
447
448 return $bundle_id;
449 }
450 }
451 }
452 // TODO: report failure to connect to facebook api
453 drupal_set_message(t('Failed to connect to Facebook for application %app.',
454 array('%app' => $fb_app->title)), 'error');
455 }
456
457 function fb_feed_insert($node) {
458
459 // Register the new template with facebook.
460 $bundle_id = fb_feed_register_template($node);
461 if (!$bundle_id)
462 // Show an error, but save the node anyway.
463 drupal_set_message(t('Failed to register template bundle with Facebook. This template bundle may not be used.'), 'error');
464
465 $data = serialize($node->fb_feed_data);
466
467 // We're going to save the apikey, although it may be redundant with the
468 // fb_app_nid, so that we can detect any inconsistencies (i.e. the apikey
469 // has changed on us).
470 $fb_app = fb_get_app(array('nid' => $node->fb_app_nid));
471
472 db_query("INSERT INTO {fb_feed_template} (nid, fb_app_nid, label, apikey, bundle_id, fb_feed_data) VALUES (%d, %d, '%s', '%s', %f, '%s')",
473 $node->nid, $node->fb_app_nid, $node->label, $fb_app->apikey, $bundle_id, $data);
474
475 if ($bundle_id)
476 watchdog('fb_feed', 'Registered Facebook feed template bundle %id for application %app',
477 array('%id' => $bundle_id,
478 '%app' => $fb_app->title,
479 ));
480 }
481
482 function fb_feed_update($node) {
483 //dpm($node, "fb_feed_update");
484
485 // deactivate the previously registered bundle
486 if ($node->bundle_id) {
487 fb_feed_deactivate_template($node->fb_app_nid, $node->bundle_id);
488 unset($node->bundle_id);
489 }
490
491 // Register the new template with facebook.
492 $bundle_id = fb_feed_register_template($node);
493 if (!$bundle_id)
494 // Show an error, but save the node anyway.
495 drupal_set_message(t('Failed to register template bundle with Facebook. This template bundle may not be used.'), 'error');
496
497 $data = serialize($node->fb_feed_data);
498
499 // We're going to save the apikey, although it may be redundant with the
500 // fb_app_nid, so that we can detect any inconsistencies (i.e. the apikey
501 // has changed on us).
502 $fb_app = fb_get_app(array('nid' => $node->fb_app_nid));
503
504 db_query("UPDATE {fb_feed_template} SET fb_app_nid=%d, label='%s', apikey='%s', bundle_id=%f, fb_feed_data='%s' WHERE nid=%d",
505 $node->fb_app_nid, $node->label, $fb_app->apikey, $bundle_id, $data, $node->nid);
506
507 if ($bundle_id)
508 watchdog('fb_feed', 'Updated Facebook feed template bundle %id for application %app',
509 array('%id' => $bundle_id,
510 '%app' => $fb_app->title,
511 ));
512
513 }
514
515 function fb_feed_delete($node) {
516 // TODO: deactivate the previously registered bundle
517 db_query('DELETE FROM {fb_feed_template} WHERE nid=%d',
518 $node->nid);
519 }
520
521 function fb_feed_load($node) {
522 $data = db_fetch_array(db_query('SELECT fb_app_nid, label, apikey, bundle_id, fb_feed_data FROM {fb_feed_template} WHERE nid=%d',
523 $node->nid));
524 $data['fb_feed_data'] = unserialize($data['fb_feed_data']);
525 return $data;
526 }
527
528 // TODO: hook_nodeapi. React appropriately if fb_app apikey changes or is deleted.
529
530 function fb_feed_view($node, $teaser=FALSE, $page=FALSE) {
531 $node = node_prepare($node, $teaser);
532 $fb_app = fb_get_app(array('nid' => $node->fb_app_nid));
533
534 if (!$node->bundle_id)
535 drupal_set_message(t('The template shown must be registered with Facebook before it can be used.'), 'error');
536
537 $items = array(t('Application') => l($fb_app->title, 'node/'.$fb_app->nid),
538 t('Label') => $node->label,
539 t('Template Bundle ID') => $node->bundle_id ? $node->bundle_id : '<em>'.t('Template bundle not registered') . '</em>',
540 );
541 $items[t('One-line templates')] = implode("<br />\n",
542 array_map('check_plain', $node->fb_feed_data['line']));
543 foreach ($node->fb_feed_data['short'] as $key => $template) {
544 if (is_array($template))
545 $items[t('Short template %num', array('%num' => $key +1))] =
546 implode("<br />\n", array_map('check_plain', $node->fb_feed_data['short'][$key]));
547 }
548
549 if ($node->fb_feed_data['full']['template_title']) {
550 $items[t('Full template')] =
551 implode("<br />\n", array_map('check_plain', $node->fb_feed_data['full']));
552 }
553 $node->content['fb_feed'] = array('#value' => theme('dl', $items));
554 return $node;
555 }
556
557
558 /**
559 * Publish a feed message via the Feed Dialog. This will prompt the
560 * user before writing to the feed, and this is Facebook's recommended
561 * technique. The other option is publishUserAction.
562 *
563 * Our approach here is like drupal_set_message. We save the data to
564 * the session, and show the user the dialog on the next page request.
565 */
566 function fb_feed_show_dialog($label, $tokens, $options) {
567 if (!isset($_SESSION['fb_feed_dialogs']))
568 $_SESSION['fb_feed_dialogs'] = array();
569
570 // Accept either label or template as first param.
571 if (is_object($label)) {
572 $template = $label;
573 }
574 else {
575 $template = fb_feed_get_template_by_label($label);
576 }
577
578 if ($template) {
579 // Change token data structure.
580 if (is_object($tokens) && is_array($tokens->tokens)) {
581 foreach ($tokens->tokens as $i => $key) {
582 $fb_tokens[$key] = $tokens->values[$i];
583 }
584 }
585 else {
586 $fb_tokens = $tokens;
587 }
588
589
590 if (!isset($_SESSION['fb_feed_dialogs'][$template->apikey])) {
591 $_SESSION['fb_feed_dialogs'][$template->apikey] = array();
592 }
593
594 $_SESSION['fb_feed_dialogs'][$template->apikey][] = array(
595 'fb_feed_nid' => $template->nid,
596 'apikey' => $template->apikey,
597 'bundle_id' => $template->bundle_id,
598 'tokens' => $fb_tokens,
599 'options' => $options,
600 );
601 }
602 else {
603 // TODO: log error.
604 }
605 }
606
607 /**
608 * Get the data for one or more feed dialogs. Use this function in
609 * ajax callbacks, where you want to publish dialog(s) in response to
610 * javascript events. Json encode the result, and pass it to
611 * Fb_Feed.show_feed_dialog().
612 */
613 function fb_feed_get_show_dialog_data($fb_app = null) {
614 if (!$fb_app)
615 $fb_app = $GLOBALS['fb_app'];
616
617 $data = $_SESSION['fb_feed_dialogs'][$fb_app->apikey];
618 unset($_SESSION['fb_feed_dialogs'][$fb_app->apikey]);
619 return $data;
620 }
621
622
623 /**
624 * Implementation of hook_fb().
625 */
626 function fb_feed_fb($op, $data, &$return) {
627 if ($op == FB_OP_CONNECT_JS_INIT || $op == FB_OP_CANVAS_FBJS_INIT) {
628 // This is our chance to add feed dialog to the current page.
629
630 if (isset($_SESSION['fb_feed_dialogs']) &&
631 isset($_SESSION['fb_feed_dialogs'][$data['fb_app']->apikey])) {
632 // Use javascript to publish feed messages
633 foreach ($_SESSION['fb_feed_dialogs'][$data['fb_app']->apikey] as $d) {
634
635 // Note that FB.Connect.ShowFeedDialog and Facebook.showFeedDialog take different parameters. Thanks, facebook!
636
637 if ($op == FB_OP_CONNECT_JS_INIT) {
638 // http://wiki.developers.facebook.com/index.php/JS_API_M_FB.Connect.ShowFeedDialog
639 $args = array($d['bundle_id']);
640 foreach (array('template_data' => '{}',
641 'target_ids' => 'null',
642 'body_general' => 'null',
643 'story_size' => 'null',
644 'require_connect' => 'null',
645 'callback' => 'null',
646 'user_message_prompt' => 'null',
647 'user_message' => 'null',
648 ) as $key => $default) {
649 if (isset($d['options'][$key])) {
650 if (in_array($key, array('require_connect', 'callback')))
651 $args[] = $d['options'][$key]; // no json_encode
652 else if ($key == 'user_message' && is_string($d['options'][$key])) {
653 $args[] = '{value:' . json_encode($d['options'][$key]) . '}';
654 }
655 else
656 $args[] = json_encode($d['options'][$key]);
657 } else
658 $args[] = $default;
659 }
660 $return[] = "FB.Connect.showFeedDialog(" . implode(',', $args) . ");";
661 }
662 else if ($op == FB_OP_CANVAS_FBJS_INIT) {
663 // http://wiki.developers.facebook.com/index.php/Facebook.showFeedDialog
664 $args = array($d['bundle_id']);
665 foreach (array('template_data' => 'null',
666 'body_general' => 'null',
667 'target_id' => 'null', // note target_id int
668 'continuation' => 'null',
669 'user_message_prompt' => 'null',
670 'user_message' => 'null',
671 ) as $key => $default) {
672 if (isset($d['options'][$key])) {
673 if (in_array($key, array('continuation')))
674 $args[] = $d['options'][$key]; // no json_encode
675 else if ($key == 'user_message' && is_string($d['options'][$key])) {
676 $args[] = '{value:' . json_encode($d['options'][$key]) . '}';
677 }
678 else
679 $args[] = json_encode($d['options'][$key]);
680 } else
681 $args[] = $default;
682 }
683
684 $return[] = "Facebook.showFeedDialog(" . implode(",\n", $args) . ");";
685
686 }
687 }
688 //dpm($return, 'fb_feed_fb debug');
689
690 // remove displayed items from session
691 unset($_SESSION['fb_feed_dialogs'][$data['fb_app']->apikey]);
692 }
693 }
694 }
695
696
697 //// Feed Actions
698 function fb_feed_action_info() {
699 $items = array();
700 $items['fb_feed_action_publish'] = array(
701 'type' => 'fb_feed_action',
702 'description' => t('Facebook Feed: publishUserAction'),
703 'configurable' => TRUE,
704 // Include 'custom' so third-party modules can define their own triggers.
705 'hooks' => array(
706 'nodeapi' => array('delete', 'insert', 'update', 'view', 'custom'),
707 'comment' => array('delete', 'insert', 'update', 'view', 'custom'),
708 'user' => array('insert', 'update', 'delete', 'login', 'logout', 'custom'),
709 ),
710 );
711 $items['fb_feed_action_show_dialog'] = array(
712 'type' => 'fb_feed_action_show_dialog',
713 'description' => t('Facebook Feed: show dialog'),
714 'configurable' => TRUE,
715 // Include 'custom' so third-party modules can define their own triggers.
716 'hooks' => array(
717 'nodeapi' => array('delete', 'insert', 'update', 'view', 'custom'),
718 'comment' => array('delete', 'insert', 'update', 'view', 'custom'),
719 'user' => array('insert', 'update', 'delete', 'login', 'logout', 'custom'),
720 ),
721 );
722
723
724 return $items;
725 }
726
727 function fb_feed_get_template_options() {
728 $options = array(0 => t('<please choose>'));
729 $result = db_query(db_rewrite_sql("SELECT DISTINCT n.nid, n.title FROM {node} n WHERE n.type='%s' AND n.status=1"), FB_FEED_NODE_TYPE_TEMPLATE);
730 while ($data = db_fetch_object($result)) {
731 $options[$data->nid] = $data->title;
732 }
733 return $options;
734 }
735
736 function fb_feed_action_publish_form($values) {
737 drupal_set_message(t('Note that Facebook recommends showFeedDialog over publishUserAction. Consider using the show feed dialog action instead.'));
738 // Allow user to choose amoung all available templates.
739 $options = fb_feed_get_template_options();
740 $form['description'] = array('#value' => t('Note that this action will only succeed when executed from a Facebook canvas page when the user is logged in, or on non-canvas pages when the local user has authorized offline access. These are privacy restrictions enforced by the Facebook API.'));
741 $form['fb_feed_template_nid'] =
742 array('#type' => 'select',
743 '#title' => t('Template'),
744 '#default_value' => $values['fb_feed_template_nid'],
745 '#options' => $options,
746 '#description' => t('Which template will we be using?'),
747 '#required' => TRUE,
748 );
749
750 $form['token_enable'] =
751 array('#type' => 'checkbox',
752 '#title' => t('Use token replacement'),
753 '#default_value' => $values['token_enable'],
754 '#description' => t('Use token module for template substitution.'),
755 );
756
757 $form['hook_help'] =
758 array('#type' => 'markup',
759 '#value' => t('In addition to token replacement, <em>hook_fb_feed</em> will be called before Feed.publishUserAction is called. This hook gives you an oportunity to customize the parameters that will be passed to Facebook, for example to provide values for custom tokens in your template. Until further documentation is provided, you should look in the code for exactly how these functions are called.'),
760 '#prefix' => '<p>',
761 '#suffix' => '</p>',
762 );
763 return $form;
764 }
765
766 function fb_feed_action_publish_validate($form_id, $values) {
767 // TODO
768 }
769
770 function fb_feed_action_publish_submit($form_id, $state) {
771 $items = array();
772 foreach (array('fb_feed_template_nid', 'token_enable') as $key) {
773 $items[$key] = $state['values'][$key];
774 }
775 return $items;
776 }
777
778 function fb_feed_action_publish(&$context, $values = array()) {
779 //dpm(func_get_args(), 'fb_actions_minifeed');
780
781 // Get the objects we're acting upon.
782 $objects = array();
783 if ($values['hook'] == 'nodeapi') {
784 $objects['node'] = $values['node'];
785 }
786 else if ($values['hook'] == 'comment') {
787 $objects['comment'] = $values['comment'];
788 $objects['node'] = node_load($values['comment']->nid);
789 }
790 else if ($values['hook'] == 'user') {
791 $account = $values['user'];
792 $objects['user'] = $account;
793 }
794
795 // Get the template
796 $template = node_load($values['fb_feed_template_nid']);
797 // TODO: Sanity check that bundle has been registered with Facebook.
798 // And the app
799 $fb_app = fb_get_app(array('nid' => $template->fb_app_nid));
800 $objects['fb_app'] = $fb_app;
801 // TODO: Sanity check that apikeys match.
802
803
804 // Log into facebook as the current user.
805 if ($GLOBALS['fb_app'] && ($fb_app->nid = $GLOBALS['fb_app']->nid))
806 // We're in a canvas page for the desired app. We're already logged in.
807 $fb = $GLOBALS['fb'];
808 else {
809 global $user;
810 // Must log in to publish user action. This will only work if the user
811 // have authorized us for offline access.
812 $fbu = fb_get_fbu($user->uid, $fb_app);
813 if ($fbu) {
814 $fb = fb_api_init($fb_app, $fbu);
815 }
816 }
817
818 // It's possible we have an $fb, but session may not be valid. TODO: find a
819 // way to test this?
820 if ($fb) {
821
822 // We need to pass a bunch of parameters to Feed.publishUserAction.
823 $params = array('bundle_id' => $template->bundle_id,
824 'tokens' => array(),
825 'target_ids' => array(),
826 'body_general' => '',
827 'do_publish' => TRUE,
828 );
829 $options = array('fb_feed_template' => $template);
830
831 if ($values['token_enable']) {
832 // Use tokens for every kind of object we know about
833 foreach (array('node', 'comment', 'user', 'fb_app') as $type) {
834 if ($objects[$type]) {
835 $toks = token_get_values($type, $objects[$type], FALSE, $options);
836 //watchdog('fb_feed_debug', "token_get_values($type) returned " . print_r($toks, 1));
837 if ($toks && is_array($toks->tokens)) {
838 foreach ($toks->tokens as $i => $key)
839 $params['tokens'][$key] = $toks->values[$i];
840 }
841 }
842 }
843 }
844
845 // Use naming conventions to allow tokens to provide every parameter.
846 if ($params['tokens']['target_ids'])
847 $params['target_ids'][] = $params['tokens']['target_ids'];
848 if ($params['tokens']['body_general'])
849 $params['body_general'] = $params['tokens']['body_general'];
850
851 // Invoke a hook so that other modules have a chance to modify the params before we pass them to facebook.
852 $params = fb_feed_invoke($fb_app, FB_FEED_OP_TOKEN_ALTER,
853 $params, array('fb_feed_template' => $template,
854 'objects' => $objects,
855 'context' => $context));
856
857 if (fb_verbose()) {
858 watchdog('fb_feed', "Publish user action, app is %app, bundle is %bundle_id, params are !params.",
859 array('%app' => $fb_app->title,
860 '%bundle_id' => $template->bundle_id,
861 '!params' => print_r($params, 1)));
862 }
863 if (is_array($params['tokens']) && $params['do_publish']) {
864 if (is_array($params['target_ids']))
865 $target_ids = implode(',', $params['target_ids']);
866 else
867 $target_ids = $params['target_ids'];
868
869 try {
870 // http://wiki.developers.facebook.com/index.php/Feed.publishUserAction
871 //if (FALSE) // disabled for debugging
872 $fb->api_client->feed_publishUserAction($template->bundle_id,
873 json_encode($params['tokens']),
874 $target_ids,
875 $params['body_general']);
876 } catch (Exception $e) {
877 // We can get a lot of "Feed action limit reached" exceptions. So
878 // only log if fb_verbose is true.
879 if ($e->getCode() != 341 || fb_verbose())
880 fb_log_exception($e, t('Failed to publish user action'));
881 }
882 }
883 }
884
885 }
886
887
888 // show feed dialog action
889
890 function fb_feed_action_show_dialog_form($values) {
891 // Allow user to choose from all available templates.
892 $options = fb_feed_get_template_options();
893 $form['description'] = array(
894 '#value' => t('Note that this action will only succeed when executed from a Facebook canvas page or Facebook Connect page when the user is logged in. These are privacy restrictions enforced by the Facebook API.'));
895 $form['fb_feed_template_nid'] = array(
896 '#type' => 'select',
897 '#title' => t('Template'),
898 '#default_value' => $values['fb_feed_template_nid'],
899 '#options' => $options,
900 '#description' => t('Which template will we be using?'),
901 '#required' => TRUE,
902 );
903
904 $form['token_enable'] = array(
905 '#type' => 'checkbox',
906 '#title' => t('Use token replacement'),
907 '#default_value' => $values['token_enable'],
908 '#description' => t('Use token module for template substitution. <br/><strong>Bugs in FBJS may cause cryptic errors when publishing feed dialogs on canvas pages.</strong> This feature will work with Facebook Connect. On FBML canvas pages your mileage may vary. If problems arise, leave this unchecked and implement hook_fb_feed_show_dialog_alter() instead.'),
909 );
910
911 $form['user_message_prompt'] = array(
912 '#type' => 'textfield',
913 '#title' => t('Prompt'),
914 '#description' => t('The label (which could be in the form of a question) that appears above the text box on the Feed form next to the Facebook-provided default, "Write Something..."'),
915 '#default_value' => $values['user_message_prompt'],
916 );
917
918 $form['hook_help'] = array(
919 '#type' => 'markup',
920 '#value' => t('In addition to token replacement, <em>drupal_alter(\'fb_feed_show_dialog\', ...)</em> will be called before fb_feed_show_dialog. This hook gives your custom module an opportunity to change any values before they are passed to Facebook\'s API.'),
921 '#prefix' => '<p>',
922 '#suffix' => '</p>',
923 );
924 return $form;
925 }
926
927 function fb_feed_action_show_dialog_validate($form_id, $values) {
928 // TODO
929 }
930
931 function fb_feed_action_show_dialog_submit($form_id, $state) {
932 $items = array();
933 foreach (array('fb_feed_template_nid', 'token_enable', 'user_message_prompt') as $key) {
934 $items[$key] = $state['values'][$key];
935 }
936 return $items;
937 }
938
939 function fb_feed_action_show_dialog(&$context, $values = array()) {
940 // Get the template
941 $template = node_load($values['fb_feed_template_nid']);
942 // Params we will pass to alter hook, then to fb_feed_show_dialog
943 $params = array(
944 'template' => $template,
945 'tokens' => array(),
946 'options' => array(
947 'user_message_prompt' => $values['user_message_prompt'],
948 ),
949 'cancel' => FALSE, // Allow alter hook to cancel dialog.
950 );
951
952 // Get the objects we're acting upon.
953 $objects = array();
954 if ($values['hook'] == 'nodeapi') {
955 $objects['node'] = $values['node'];
956 $params['options']['user_message'] = check_plain($objects['node']->body);
957 }
958 else if ($values['hook'] == 'comment') {
959 $objects['comment'] = $values['comment'];
960 $objects['node'] = node_load($values['comment']->nid);
961 $params['options']['user_message'] = check_plain($objects['comment']->comment);
962 }
963 else if ($values['hook'] == 'user') {
964 $account = $values['user'];
965 $objects['user'] = $account;
966 }
967
968 // TODO: Sanity check that bundle has been registered with Facebook.
969 // And the app
970 $fb_app = fb_get_app(array('nid' => $template->fb_app_nid));
971 $objects['fb_app'] = $fb_app;
972 // TODO: Sanity check that apikeys match.
973
974
975 if ($values['token_enable']) {
976 // Use tokens for every kind of object we know about
977 foreach (array('node', 'comment', 'user', 'fb_app') as $type) {
978 if ($objects[$type]) {
979 $toks = token_get_values($type, $objects[$type], FALSE, $options);
980 //watchdog('fb_feed_debug', "token_get_values($type) returned " . print_r($toks, 1));
981 if ($toks && is_array($toks->tokens)) {
982 foreach ($toks->tokens as $i => $key)
983 $params['options']['template_data'][$key] = $toks->values[$i];
984 }
985 }
986 }
987 }
988
989
990 // Invoke a hook so that other modules have a chance to modify the params before we pass them to facebook.
991 drupal_alter('fb_feed_show_dialog', $params,
992 array('context' => $context,
993 'objects' => $objects));
994
995 if (!$params['cancel']) {
996 fb_feed_show_dialog($params['template'], $params['tokens'], $params['options']);
997 }
998
999 }
1000
1001
1002
1003
1004
1005
1006
1007
1008 /**
1009 * Implementation of hook_fb_feed
1010 */
1011 function fb_feed_fb_feed($fb_app, $op, &$return, $data) {
1012 if ($op == FB_FEED_OP_TOKEN_ALTER) {
1013 // In this hook, we have a chance to add tokens before a user action is
1014 // published.
1015 if ($return['tokens']['nid'] && $return['tokens']['title'] &&
1016 $return['tokens']['fb-app-url']) {
1017 // Add a link to a canvas page
1018 $return['tokens']['fb-feed-node-link'] = l($return['tokens']['title-raw'],
1019 $return['tokens']['fb-app-url'].'/node/'.$return['tokens']['nid'],
1020 array(), NULL, NULL, NULL, TRUE);
1021 }
1022 if ($return['tokens']['fb-app-title'] && $return['tokens']['fb-app-url']) {
1023 $return['tokens']['fb-feed-app-link'] = l($return['tokens']['fb-app-title'],
1024 $return['tokens']['fb-app-url'],
1025 array(), NULL, NULL, NULL, TRUE);
1026 }
1027 }
1028 }
1029
1030 /**
1031 * Invoke hook_fb_feed.
1032 */
1033 function fb_feed_invoke($fb_app, $op, $return = NULL, $data = NULL) {
1034 foreach (module_implements(FB_FEED_HOOK) as $name) {
1035 $function = $name . '_' . FB_FEED_HOOK;
1036 $function($fb_app, $op, $return, $data);
1037 }
1038 return $return;
1039 }
1040
1041 /**
1042 * Helper to get feed data programatically.
1043 */
1044 function fb_feed_get_template_by_label($label) {
1045 $result = db_query("SELECT fbf.*, n.title FROM {fb_feed_template} fbf INNER JOIN {node} n ON n.nid = fbf.nid WHERE n.status=1 AND fbf.label='%s'",
1046 $label);
1047 return db_fetch_object($result);
1048 }
1049
1050 /**
1051 * Implementation of hook_fb_feed_show_dialog_alter()
1052 *
1053 * This is called before a feed dialog is shown. Here we provide some
1054 * of the most commonly used tokens.
1055 *
1056 */
1057 function fb_feed_fb_feed_show_dialog_alter(&$params, $data) {
1058 // TODO: this function is canvas-centric. Need to add Facebook Connect support.
1059
1060 // Uncomment the dpm() function to see what's getting passed to this function. Useful when implementing/debugging.
1061 //dpm(func_get_args(), "fb_feed_fb_feed_show_dialog_alter");
1062
1063 // Due to bugs in facebook's FBJS, we can't rely on token substitution. So we must provide values for all our feed template tokens here.
1064
1065 if ($fb_app = $data['objects']['fb_app']) {
1066 if ($fb_app->canvas) {
1067 $params['options']['template_data']['fb-feed-canvas-app-link'] =
1068 l($fb_app->title,
1069 'http://apps.facebook.com/'.$fb_app->canvas);
1070 }
1071
1072 // Image TODO
1073 /**
1074 $params['options']['template_data']['images'] = array(
1075 array('src' => IMAGE URL HERE XXX
1076 'href' => 'http://apps.facebook.com/' . $fb_app->canvas,
1077 ),
1078 );
1079 **/
1080 // $data['objects']['node'] is set if the Action was triggered by a node event.
1081 if ($node = $data['objects']['node']) {
1082 if ($fb_app->canvas) {
1083 $params['options']['template_data']['fb-feed-canvas-node-link'] =
1084 l($node->title,
1085 'http://apps.facebook.com/'.$fb_app->canvas . '/node/' . $node->nid);
1086 }
1087 $params['options']['template_data']['fb-feed-node-teaser'] =
1088 check_plain($node->teaser);
1089
1090 <