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

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

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


Revision 1.17 - (show annotations) (download) (as text)
Tue Oct 27 20:39:52 2009 UTC (4 weeks, 4 days ago) by yogadex
Branch: MAIN
Changes since 1.16: +2 -2 lines
File MIME type: text/x-php
avoid use of dprint_r()
1 <?php
2
3 /**
4 * @file
5 *
6 * This module defines facebook-specific form elements for use with Drupal's
7 * form API.
8 *
9 * It also defines commonly used forms, for example a form to invite friends
10 * to install an application.
11 */
12
13 // There's some obsolete and experimental code in this module. Everything may change! Watch out for things labelled deprecated.
14
15 /**
16 * hook_menu.
17 */
18 function fb_form_menu() {
19 $items = array();
20 // Page allowing a user to invite their friends to add the app.
21 $items['fb/invite'] =
22 array('page callback' => 'fb_form_invite_page',
23 'access callback' => TRUE,
24 'type' => MENU_CALLBACK,
25 );
26 $items['fb_form_friend_selector_autocomplete'] =
27 array('page callback' => 'fb_form_friend_selector_autocomplete',
28 'access callback' => TRUE,
29 'type' => MENU_CALLBACK,
30 );
31 return $items;
32 }
33
34 /**
35 * hook_form_alter.
36 */
37 function fb_form_form_alter(&$form, &$form_state, $form_id) {
38 /* Drupal allows no clean way to set $form['#type'], so we hack... */
39 if ($type = $form['#fb_form_type_hack']) {
40 $form['#type'] = $type;
41 unset($form['#fb_form_type_hack']);
42 }
43
44 // Support for ahah (see ahah_forms.module)
45 if (function_exists('fb_canvas_is_fbml') &&
46 fb_canvas_is_fbml()) {
47 $form['#after_build'][] = 'fb_ahah_bind_form';
48 }
49
50 }
51
52 /**
53 * Support for AHAH in forms.
54 *
55 * This is intended to be compatible with the syntax of ahah_forms.module.
56 * However, much of what that module does requires jquery. In FBML we have to
57 * jquery and are therefor limited in what we can support. For example, we
58 * only support the id selectors, you can't specify selector in your
59 * ahah_bindings.
60 */
61 function fb_ahah_bind_form( $form ) {
62 static $one_time_only;
63 // Facebook javascript appears to work only when the user is logged in. Not
64 // sure whether this is on purpose or not. So we only activate AHAH when
65 // user is logged in.
66 global $fb;
67 if ($fb && $fb->api_client->added && function_exists('ahah_forms_scan_form_children')) {
68 $bindings = array();
69 ahah_forms_scan_form_children( $form, $form['#id'], $bindings );
70 //drupal_set_message( "After Scan: Wrapper Bindings = " . print_r( $bindings, TRUE ) );
71
72 if( count( $bindings ) > 0 ) {
73 if (!$one_time_only) {
74 //add in required javascript files
75 $module_path = drupal_get_path('module', 'ahah_forms');
76
77 fb_add_js(drupal_get_path('module', 'fb_form') . '/fb_ahah_forms.js', 'module');
78
79 drupal_add_js(
80 array(
81 'ahah' => array(
82 'basePaths' => array( 'base' => base_path(), 'module' => $module_path ),
83 'bindings' => array( $bindings ),
84 ),
85 ),
86 'setting', 'fbml'
87 );
88 $one_time_only = TRUE;
89 }
90 else {
91 drupal_add_js(
92 array(
93 'ahah' => array(
94 'bindings' => array( $bindings ),
95 ),
96 ),
97 'setting', 'fbml'
98 );
99
100 }
101 }
102 }
103
104 return $form;
105 }
106
107
108 /**
109 * Create a page to invite friends to add an app.
110 *
111 * This page will succeed only if:
112 * - shown on a canvas page
113 * - it is an FBML canvas page, not an iframe
114 * - the current user has added the application (not sure about this)
115 */
116 function fb_form_invite_page() {
117 global $fb, $fb_app;
118
119 if (function_exists('fb_canvas_is_fbml') &&
120 !fb_canvas_is_fbml()) {
121 drupal_set_message('Unable to display page. FBML required.', 'error');
122 drupal_not_found();
123 exit();
124 }
125
126 if ($fb_app) {
127 drupal_set_title(t('Invite friends to use %application',
128 array('%application' => $fb_app->title)));
129 }
130
131 $output = drupal_get_form('fb_form_multi_add_invite_form');
132
133 return $output;
134 }
135
136
137 /**
138 * Create a form allowing the user to invite friends to add the app.
139 *
140 * Facebook provides a very specific way to build this form using FBML. This will only display properly on FBML canvas pages.
141 * The FBML for this form requires the <fb:request-form> tag where the <form> tag would normally be. Also the form provides its own buttons. These two things make it difficult to use Drupal's Form API to build the form. Still, we use FAPI, because we want modules to be able to alter the form (i.e. to add descriptive text). Alteration that rely on #submit or even additional input fields will probably not work properly, however.
142 */
143 function fb_form_multi_add_invite_form() {
144 global $fb, $fb_app;
145
146 // TODO: confirm that we're displaying an FBML canvas page.
147
148 if ($fbu = fb_facebook_user($fb)) {
149 // Exclude friends who have already installed app.
150 // http://wiki.developers.facebook.com/index.php/Fb:request-form
151 $rs = $fb->api_client->fql_query("SELECT uid FROM user WHERE has_added_app=1 and uid IN (SELECT uid2 FROM friend WHERE uid1 = $fbu)");
152 $arFriends = "";
153 // Build an delimited list of users...
154 if ($rs) {
155 $arFriends .= $rs[0]["uid"];
156 for ( $i = 1; $i < count($rs); $i++ ) {
157 if ( $arFriends != "" )
158 $arFriends .= ",";
159 $arFriends .= $rs[$i]["uid"];
160 }
161 }
162 }
163
164 // Use node body in invite message.
165 $node = node_load($fb_app->nid);
166 $node = node_prepare($node);
167 $content = $node->teaser;
168
169 // Do we need to append &next=[someURL] to the url here?
170 $content .= "<fb:req-choice url=\"http://www.facebook.com/add.php?api_key={$fb_app->apikey}\" label=\"" . t('Add !title application.',
171 array('!title' => $fb_app->title)) . "\" />";
172
173 // form type fb:request-form
174 $form = array('#fb_form_type_hack' => 'fb_form_request', /* becomes #type during form_alter */
175 '#attributes' => array('type' => $fb_app->title,
176 'content' => htmlentities($content),
177 'invite' => 'true',
178 ),
179 '#action' => 'http://apps.facebook.com/' . $fb_app->canvas,
180 );
181
182 $form['friends'] =
183 array('#type' => 'fb_form_request_selector',
184 '#title' => t('Select the friends to invite.'),
185 '#attributes' => array('exclude_ids' => $arFriends),
186 );
187
188 return $form;
189 }
190
191 /**
192 * Helper function to produce a request or invite form. Note that this does
193 * not produce a full form (i.e. never use
194 * drupal_get_form('fb_form_request_form')). The caller is expected to fill
195 * out the rest of the form before returning it for use with drupal_get_form.
196 *
197 * DEPRECATED
198 */
199 function fb_form_request_form($config = array()) {
200 global $fb, $fb_app; // only works on canvas pages.
201
202 // Default config
203 $config = array_merge(array('type' => $fb_app->title,
204 'content' => 'INVITE CONTENT XXX',
205 'action' => 'http://apps.facebook.com/' . $fb_app->canvas,
206 'invite' => 'true',
207 'method' => 'POST',
208 ), $config);
209
210 // form type fb:request-form
211 $form = array('#fb_form_type_hack' => 'fb_form_request', /* becomes #type during form_alter */
212 '#attributes' => array('type' => $config['type'],
213 'content' => htmlentities($config['content']),
214 'invite' => $config['invite'],
215 ),
216 '#action' => $config['action'],
217 );
218
219 // Caller must add fb:multi-friend-selector or some other selector.
220
221 return $form;
222 }
223
224
225 /**
226 * Based on theme_form, this renders an fb:request-form.
227 */
228 function theme_fb_form_request($element) {
229 // TODO: verify attributes required by facebook are found.
230
231 // Anonymous div to satisfy XHTML compliance.
232 $action = $element['#action'] ? 'action="' . check_url($element['#action']) . '" ' : '';
233 $output = '<fb:request-form '. $action .' method="'. $element['#method'] .'" '. 'id="'. $element['#id'] .'"'. drupal_attributes($element['#attributes']) .">\n<div>". $element['#children'] ."\n</div></fb:request-form>\n";
234
235 return $output;
236 }
237
238 // Because the fb:request-form includes its own buttons, including the particularly annoying skip button, this submit callback is not used. Will probably delete it. If I can't make it work properly.
239 function fb_form_multi_add_invite_form_submit() {
240 //watchdog('fb_debug', 'fb_form_multi_add_invite_form_submit' . print_r(func_get_args(), 1));
241 //return "foo/bar";
242 }
243
244 function fb_form_group_options($fbu) {
245 $groups = fb_get_groups_data($fbu);
246 $items = array();
247 if ($groups && count($groups))
248 foreach ($groups as $data) {
249 $items[$data['gid']] = $data['name'];
250 }
251 // TODO: alphabetize list
252 return $items;
253 }
254
255 // TODO: make this work whether in canvas page or not.
256 function fb_form_friend_options($fbu) {
257 global $fb;
258 $items = array();
259
260 if ($fb) {
261 $query = "SELECT last_name, first_name, uid, pic_square FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=$fbu)"; //TODO: db_query this to be safe?
262 $result = $fb->api_client->fql_query($query);
263
264 // TODO: sort results by name
265 foreach ($result as $data) {
266 $items[$data['uid']] = $data['first_name'] . ' ' . $data['last_name'];
267 }
268 }
269 return $items;
270 }
271
272 function fb_form_group_member_options($fbg, $fbu) {
273 global $fb;
274
275 $query = "SELECT uid FROM group_member WHERE gid=$fbg"; //TODO: db_query this?
276 $result = $fb->api_client->fql_query($query);
277 drupal_set_message("fb_form_group_member_options($fbg, $fbu) query $query returns" . dpr($result, 1));
278
279
280
281
282 $query = "SELECT uid, first_name, last_name FROM user WHERE uid IN (SELECT uid FROM group_member WHERE gid=$fbg)"; //TODO: db_query to be safe?
283 $result = $fb->api_client->fql_query($query);
284 drupal_set_message("fb_form_group_member_options($fbg, $fbu) query $query returns" . dpr($result, 1));
285
286 // TODO: sort results by name
287 $options = array();
288 foreach ($result as $data) {
289 if ($data['uid'] != $fbu)
290 $options[$data['uid']] = $data['first_name'] . ' ' . $data['last_name'];
291 }
292 return $options;
293 }
294
295 function fb_form_elements() {
296 $items = array();
297
298 $items['fb_form_request_selector'] = array('#input' => TRUE,
299 '#tree' => TRUE, /* not sure what this does */
300 '#process' => array('fb_form_process_request_selector' => array()),
301 // The submit callback does not work properly in <fb:request-form>
302 '#executes_submit_callback' => TRUE,
303 );
304
305 $items['fb_form_friend_selector'] = array('#input' => TRUE,
306 '#tree' => TRUE,
307 '#process' => array('fb_form_friend_selector_process' => array()),
308 );
309
310 return $items;
311 }
312
313 /**
314 * Build a friend selector for use in <fb:request-form>.
315 *
316 * Use this to select friends when sending an invite or request.
317 */
318 function fb_form_process_request_selector($orig) {
319 global $fb;
320 // replace with FBML markup
321 $element = array('#type' => 'markup',
322 '#value' => '<fb:multi-friend-selector ',
323 );
324 if (!$orig['#attributes'])
325 $orig['#attributes'] = array();
326
327 // Use title for actiontext
328 if (!$orig['#attributes']['actiontext'])
329 $orig['#attributes']['actiontext'] = $orig['#title'];
330
331 $element['#value'] .= drupal_attributes($orig['#attributes']);
332
333 // Some settings for FAPI.
334 foreach (array('#parents', '#weight', '#name', '#id', '#input', '#required') as $key)
335 if (isset($orig[$key]))
336 $element[$key] = $orig[$key];
337
338 $element['#value'] .= ' />'; /* close tag */
339
340 return $element;
341 }
342
343 /**
344 * A selector allowing the user to choose from their friends. This must
345 * behave differently depending on whether the form is displayed on an FBML
346 * canvas page, iframe canvas page, or regular HTML page.
347 */
348 function fb_form_friend_selector_process($orig) {
349 // TODO: use fb:friend-selector on FBML pages.
350
351 // TODO: support fb_app specified in element. Perhaps using Facebook Connect.
352 if (!$fb)
353 $fb = $GLOBALS['fb']; // Global is set on canvas pages.
354
355 if (!$fb)
356 // TODO: Generate an error.
357 return;
358
359 static $options = NULL;
360 if (!$options) {
361 $query = "SELECT name, uid, pic_square FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=".fb_facebook_user().")";
362 $result = $fb->api_client->fql_query($query);
363
364 // TODO: sort results by name
365 //$options = array();
366 //foreach ($result as $data) {
367 // $options[$data['uid']] = $data['first_name'] . ' ' . $data['last_name'];
368 //}
369
370 // Store list of friends in SESSION, so our autocomplete function will not
371 // have to query it every time.
372 $_SESSION['fb_form_friend_selector_result'] = $result;
373 }
374
375 $element = array('#validate' => array('fb_form_friend_selector_validate' => array($orig)));
376 foreach (array('#title', '#parents', '#description', '#default_value', '#weight', '#multiple', '#required', '#name', '#value', '#id', '#size', '#rows', '#validate') as $key)
377 if (isset($orig[$key]))
378 $element[$key] = $orig[$key];
379
380 // Allow use of textarea instead of textfield, autocomplete will not work.
381 if (isset($orig['#rows']) && $orig['#rows'] > 0)
382 $element['#type'] = 'textarea';
383 else
384 $element['#type'] = 'textfield';
385
386 $element['#autocomplete_path'] = url('fb_form_friend_selector_autocomplete', array('absolute' => TRUE));
387 return $element;
388 }
389
390 /**
391 * Autocomplete friend names
392 */
393 function fb_form_friend_selector_autocomplete($string) {
394 // Regexp copied from taxonomy_autocomplete
395 $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
396 preg_match_all($regexp, $string, $preg_matches);
397 $typed_names = $preg_matches[1];
398 $last_string = trim(array_pop($typed_names));
399 $prefix = count($typed_names) ? implode(', ', $typed_names) .', ' : '';
400
401 // Get list of friends from session
402 $result = $_SESSION['fb_form_friend_selector_result'];
403
404 $matches = array();
405 if (count($result) && $last_string) {
406 foreach ($result as $data) {
407 $name = strtolower($data['name']);
408 if (strpos($name, strtolower($last_string)) !== FALSE &&
409 !in_array($data['name'], $typed_names)) {
410 $markup = "<img src={$data[pic_square]} />{$data[name]}";
411 $matches[$prefix . $data['name']] = $markup;
412 }
413 }
414 }
415
416 if (count($matches))
417 print drupal_to_js($matches);
418
419 exit();
420 }
421
422 /**
423 * Convert #default_value into a value for the form field
424 */
425 function fb_form_friend_selector_value(&$element) {
426 //drupal_set_message("friend_selector_value" . dpr($form, 1));
427 // default value passed in will be an array of ids, we need to display a comma seperated list of names.
428 $info = fb_users_getInfo($element['#default_value']);
429 $items = array();
430 foreach ($info as $data) {
431 $items[] = $data['name'];
432 }
433 if (count($items))
434 $element['#value'] = implode(', ', $items);
435 }
436
437 function fb_form_friend_selector_validate($element, $set_errors = TRUE) {
438 //dpm(func_get_args(), "fb_form_friend_selector_validate");
439 if (!trim($element['#value']))
440 return;
441 $names = explode(',', $element['#value']);
442 $items = array(); // Facebook user ids
443 $result = $_SESSION['fb_form_friend_selector_result'];
444 foreach ($names as $name) {
445 $found = FALSE;
446 foreach ($result as $data) {
447 $fb_name = strtolower($data['name']);
448 if (strtolower(trim($name)) == $fb_name) {
449 if ($found) {
450 // TODO: handle name collisions more gracefully!
451 if ($set_errors)
452 form_set_error(implode('][', $element['#parents']),
453 t('\'%name\' matched more than one friend.', array('%name' => $name)));
454 $found = -1;
455 }
456 else
457 $found = $data['uid'];
458 }
459 }
460 if ($found > 0) {
461 // Running list of ids
462 $items[] = $found;
463 }
464 else if ($found == 0)
465 if ($set_errors)
466 form_set_error(implode('][', $element['#parents']),
467 t('Could not find a friend named \'%name\'.', array('%name' => $name)));
468 }
469 // Make the submitted value a list of ids, not a comma-seperated list of names.
470 form_set_value($element, $items);
471 _form_set_value($_POST, $element, $element['#parents'], $items);
472 }
473
474 function fb_form_theme() {
475 return array(
476 'fb_form_multi_friend_selector' => array(
477 'arguments' => array('elements' => NULL),
478 ),
479 'fb_form_requestform' => array(
480 'arguments' => array('elements' => NULL),
481 ),
482 'fb_form_req_choice' => array(
483 'arguments' => array('elements' => NULL),
484 ),
485 'fb_form_serverfbml' => array(
486 'arguments' => array('elements' => NULL),
487 ),
488 );
489 }
490
491 function theme_fb_form_multi_friend_selector($elements) {
492 $output = "<fb:multi-friend-selector " . drupal_attributes($elements['#attributes']) . ">" . $elements['#children'] ."</fb:multi-friend-selector>\n";
493 return $output;
494 }
495
496 function theme_fb_form_requestform($elements) {
497 // content attribute is special.
498 if (is_array($elements['#attributes']['content'])) {
499 $elements['#attributes']['content'] = drupal_render($elements['#attributes']['content']);
500 }
501 $output = "<fb:request-form " . drupal_attributes($elements['#attributes']) . ">". $elements['#children'] . "</fb:request-form>\n";
502 return $output;
503 }
504
505 function theme_fb_form_req_choice($elements) {
506 // This special tag has no children
507 $output = "<fb:req-choice " . drupal_attributes($elements['#attributes']) . " />\n";
508 return $output;
509 }
510
511 function theme_fb_form_serverfbml($elements) {
512 $output = '<fb:serverfbml><script type="text/fbml">' . drupal_attributes($elements['#attributes']) . $elements['#children'] . "</script></fb:serverfbml>\n";
513 return $output;
514 }
515
516
517 function fb_form_multi_selector($attrs = array()) {
518 $element = array('#type' => 'fb_form_multi_friend_selector',
519 '#attributes' => $attrs,
520 );
521 return $element;
522 }
523
524 /**
525 * Helper function to build fb_form_request_form element.
526 */
527 function fb_form_requestform($attrs = array()) {
528 $element = array('#type' => 'fb_form_requestform',
529 '#attributes' => $attrs,
530 );
531 return $element;
532 }

  ViewVC Help
Powered by ViewVC 1.1.2