3 * Name: Facebook Connector
5 * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
9 * Installing the Friendica/Facebook connector
11 * 1. register an API key for your site from developer.facebook.com
12 * a. We'd be very happy if you include "Friendica" in the application name
13 * to increase name recognition. The Friendica icons are also present
14 * in the images directory and may be uploaded as a Facebook app icon.
15 * Use images/friendica-16.jpg for the Icon and images/friendica-128.jpg for the Logo.
16 * b. The url should be your site URL with a trailing slash.
17 * Friendica is a software application and does not require a Privacy Policy
18 * or Terms of Service, though your installation of it might. Facebook may require
19 * that you provide a Privacy Policy, which we find ironic.
20 * c. Set the following values in your .htconfig.php file
21 * $a->config['facebook']['appid'] = 'xxxxxxxxxxx';
22 * $a->config['facebook']['appsecret'] = 'xxxxxxxxxxxxxxx';
23 * Replace with the settings Facebook gives you.
24 * d. Navigate to Set Web->Site URL & Domain -> Website Settings. Set
25 * Site URL to yoursubdomain.yourdomain.com. Set Site Domain to your
27 * 2. (This step is now obsolete. Enable the plugin via the Admin panel.)
28 * Enable the facebook plugin by including it in .htconfig.php - e.g.
29 * $a->config['system']['addon'] = 'plugin1,plugin2,facebook';
30 * 3. Visit the Facebook Settings section of the "Settings->Plugin Settings" page.
31 * and click 'Install Facebook Connector'.
32 * 4. This will ask you to login to Facebook and grant permission to the
33 * plugin to do its stuff. Allow it to do so.
34 * 5. You're done. To turn it off visit the Plugin Settings page again and
35 * 'Remove Facebook posting'.
37 * Vidoes and embeds will not be posted if there is no other content. Links
38 * and images will be converted to a format suitable for the Facebook API and
39 * long posts truncated - with a link to view the full post.
41 * Facebook contacts will not be able to view private photos, as they are not able to
42 * authenticate to your site to establish identity. We will address this
43 * in a future release.
46 define('FACEBOOK_MAXPOSTLEN', 420);
49 function facebook_install() {
50 register_hook('post_local', 'addon/facebook/facebook.php', 'facebook_post_local');
51 register_hook('notifier_normal', 'addon/facebook/facebook.php', 'facebook_post_hook');
52 register_hook('jot_networks', 'addon/facebook/facebook.php', 'facebook_jot_nets');
53 register_hook('connector_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings');
54 register_hook('cron', 'addon/facebook/facebook.php', 'facebook_cron');
55 register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
59 function facebook_uninstall() {
60 unregister_hook('post_local', 'addon/facebook/facebook.php', 'facebook_post_local');
61 unregister_hook('notifier_normal', 'addon/facebook/facebook.php', 'facebook_post_hook');
62 unregister_hook('jot_networks', 'addon/facebook/facebook.php', 'facebook_jot_nets');
63 unregister_hook('connector_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings');
64 unregister_hook('cron', 'addon/facebook/facebook.php', 'facebook_cron');
65 unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
68 unregister_hook('post_local_end', 'addon/facebook/facebook.php', 'facebook_post_hook');
69 unregister_hook('plugin_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings');
73 /* declare the facebook_module function so that /facebook url requests will land here */
75 function facebook_module() {}
79 /* If a->argv[1] is a nickname, this is a callback from Facebook oauth requests. */
81 function facebook_init(&$a) {
87 $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1",
94 $auth_code = (($_GET['code']) ? $_GET['code'] : '');
95 $error = (($_GET['error_description']) ? $_GET['error_description'] : '');
99 logger('facebook_init: Error: ' . $error);
101 if($auth_code && $uid) {
103 $appid = get_config('facebook','appid');
104 $appsecret = get_config('facebook', 'appsecret');
106 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id='
107 . $appid . '&client_secret=' . $appsecret . '&redirect_uri='
108 . urlencode($a->get_baseurl() . '/facebook/' . $nick)
109 . '&code=' . $auth_code);
111 logger('facebook_init: returned access token: ' . $x, LOGGER_DATA);
113 if(strpos($x,'access_token=') !== false) {
114 $token = str_replace('access_token=', '', $x);
115 if(strpos($token,'&') !== false)
116 $token = substr($token,0,strpos($token,'&'));
117 set_pconfig($uid,'facebook','access_token',$token);
118 set_pconfig($uid,'facebook','post','1');
119 if(get_pconfig($uid,'facebook','no_linking') === false)
120 set_pconfig($uid,'facebook','no_linking',1);
122 fb_get_friends($uid);
123 fb_consume_all($uid);
132 function fb_get_self($uid) {
133 $access_token = get_pconfig($uid,'facebook','access_token');
136 $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token);
138 $j = json_decode($s);
139 set_pconfig($uid,'facebook','self_id',(string) $j->id);
145 function fb_get_friends($uid) {
147 $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
153 $access_token = get_pconfig($uid,'facebook','access_token');
155 $no_linking = get_pconfig($uid,'facebook','no_linking');
161 $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token);
163 logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA);
164 $j = json_decode($s);
165 logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA);
168 foreach($j->data as $person) {
169 $s = fetch_url('https://graph.facebook.com/' . $person->id . '?access_token=' . $access_token);
171 $jp = json_decode($s);
172 logger('fb_get_friends: info: ' . print_r($jp,true), LOGGER_DATA);
174 // always use numeric link for consistency
176 $jp->link = 'http://facebook.com/profile.php?id=' . $person->id;
178 // check if we already have a contact
180 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
187 // check that we have all the photos, this has been known to fail on occasion
189 if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro'])) {
190 require_once("Photo.php");
192 $photos = import_profile_photo('https://graph.facebook.com/' . $jp->id . '/picture', $uid, $r[0]['id']);
194 $r = q("UPDATE `contact` SET `photo` = '%s',
200 WHERE `id` = %d LIMIT 1
205 dbesc(datetime_convert()),
206 dbesc(datetime_convert()),
207 dbesc(datetime_convert()),
215 // create contact record
216 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
217 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
218 `writable`, `blocked`, `readonly`, `pending` )
219 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ",
221 dbesc(datetime_convert()),
223 dbesc(normalise_link($jp->link)),
227 dbesc('facebook ' . $jp->id),
229 dbesc(($jp->nickname) ? $jp->nickname : strtolower($jp->first_name)),
230 dbesc('https://graph.facebook.com/' . $jp->id . '/picture'),
231 dbesc(NETWORK_FACEBOOK),
232 intval(CONTACT_IS_FRIEND),
238 $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1",
248 $contact_id = $r[0]['id'];
250 require_once("Photo.php");
252 $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id);
254 $r = q("UPDATE `contact` SET `photo` = '%s',
260 WHERE `id` = %d LIMIT 1
265 dbesc(datetime_convert()),
266 dbesc(datetime_convert()),
267 dbesc(datetime_convert()),
276 // This is the POST method to the facebook settings page
277 // Content is posted to Facebook in the function facebook_post_hook()
279 function facebook_post(&$a) {
284 $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0);
285 set_pconfig($uid,'facebook','post_by_default', $value);
287 $no_linking = get_pconfig($uid,'facebook','no_linking');
289 $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0);
290 set_pconfig($uid,'facebook','no_wall',$no_wall);
292 $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0);
293 set_pconfig($uid,'facebook','private_wall',$private_wall);
296 set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps'])));
298 $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0);
299 set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1));
301 // FB linkage was allowed but has just been turned off - remove all FB contacts and posts
303 if((! intval($no_linking)) && (! intval($linkvalue))) {
304 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ",
306 dbesc(NETWORK_FACEBOOK)
309 require_once('include/Contact.php');
311 contact_remove($rr['id']);
314 elseif(intval($no_linking) && intval($linkvalue)) {
315 // FB linkage is now allowed - import stuff.
317 fb_get_friends($uid);
318 fb_consume_all($uid);
321 info( t('Settings updated.') . EOL);
327 // Facebook settings form
329 function facebook_content(&$a) {
332 notice( t('Permission denied.') . EOL);
336 if($a->argc > 1 && $a->argv[1] === 'remove') {
337 del_pconfig(local_user(),'facebook','post');
338 info( t('Facebook disabled') . EOL);
341 if($a->argc > 1 && $a->argv[1] === 'friends') {
342 fb_get_friends(local_user());
343 info( t('Updating contacts') . EOL);
347 $fb_installed = get_pconfig(local_user(),'facebook','post');
349 $appid = get_config('facebook','appid');
352 notice( t('Facebook API key is missing.') . EOL);
356 $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="'
357 . $a->get_baseurl() . '/addon/facebook/facebook.css' . '" media="all" />' . "\r\n";
359 $o .= '<h3>' . t('Facebook Connect') . '</h3>';
361 if(! $fb_installed) {
362 $o .= '<div id="facebook-enable-wrapper">';
364 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri='
365 . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Install Facebook connector for this account.') . '</a>';
370 $o .= '<div id="facebook-disable-wrapper">';
372 $o .= '<a href="' . $a->get_baseurl() . '/facebook/remove' . '">' . t('Remove Facebook connector') . '</a></div>';
374 $o .= '<div id="facebook-enable-wrapper">';
376 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri='
377 . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Re-authenticate [This is necessary whenever your Facebook password is changed.]') . '</a>';
380 $o .= '<div id="facebook-post-default-form">';
381 $o .= '<form action="facebook" method="post" >';
382 $post_by_default = get_pconfig(local_user(),'facebook','post_by_default');
383 $checked = (($post_by_default) ? ' checked="checked" ' : '');
384 $o .= '<input type="checkbox" name="post_by_default" value="1"' . $checked . '/>' . ' ' . t('Post to Facebook by default') . EOL;
386 $no_linking = get_pconfig(local_user(),'facebook','no_linking');
387 $checked = (($no_linking) ? '' : ' checked="checked" ');
388 $o .= '<input type="checkbox" name="facebook_linking" value="1"' . $checked . '/>' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ;
390 $o .= '<p>' . t('Facebook conversations consist of your <em>profile wall</em> and your friend <em>stream</em>.');
391 $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.');
392 $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '</p>';
394 $private_wall = get_pconfig(local_user(),'facebook','private_wall');
395 $checked = (($private_wall) ? ' checked="checked" ' : '');
396 $o .= '<input type="checkbox" name="facebook_private_wall" value="1"' . $checked . '/>' . ' ' . t('On this website your Facebook profile wall conversations will only be visible to you') . EOL ;
399 $no_wall = get_pconfig(local_user(),'facebook','no_wall');
400 $checked = (($no_wall) ? ' checked="checked" ' : '');
401 $o .= '<input type="checkbox" name="facebook_no_wall" value="1"' . $checked . '/>' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ;
403 $o .= '<p>' . t('If you choose to link conversations and leave both of these boxes unchecked, your Facebook profile wall will be merged with your profile wall on this website and your privacy settings on this website will be used to determine who may see the conversations.') . '</p>';
406 $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps');
408 $o .= '<div><label id="blocked-apps-label" for="blocked-apps">' . t('Comma separated applications to ignore') . ' </label></div>';
409 $o .= '<div><textarea id="blocked-apps" name="blocked_apps" >' . htmlspecialchars($blocked_apps) . '</textarea></div>';
411 $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form></div>';
419 function facebook_cron($a,$b) {
421 $last = get_config('facebook','last_poll');
423 $poll_interval = intval(get_config('facebook','poll_interval'));
425 $poll_interval = 3600;
428 $next = $last + $poll_interval;
433 logger('facebook_cron');
436 // Find the FB users on this site and randomize in case one of them
437 // uses an obscene amount of memory. It may kill this queue run
438 // but hopefully we'll get a few others through on each run.
440 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() ");
443 if(get_pconfig($rr['uid'],'facebook','no_linking'))
445 $ab = intval(get_config('system','account_abandon_days'));
447 $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1",
455 // check for new friends once a day
456 $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check');
457 if($last_friend_check)
458 $next_friend_check = $last_friend_check + 86400;
459 if($next_friend_check <= time()) {
460 fb_get_friends($rr['uid']);
461 set_pconfig($rr['uid'],'facebook','friend_check',time());
463 fb_consume_all($rr['uid']);
467 set_config('facebook','last_poll', time());
473 function facebook_plugin_settings(&$a,&$b) {
475 $b .= '<div class="settings-block">';
476 $b .= '<h3>' . t('Facebook') . '</h3>';
477 $b .= '<a href="facebook">' . t('Facebook Connector Settings') . '</a><br />';
482 function facebook_jot_nets(&$a,&$b) {
486 $fb_post = get_pconfig(local_user(),'facebook','post');
487 if(intval($fb_post) == 1) {
488 $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default');
489 $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : '');
490 $b .= '<div class="profile-jot-net"><input type="checkbox" name="facebook_enable"' . $selected . ' value="1" /> '
491 . t('Post to Facebook') . '</div>';
496 function facebook_post_hook(&$a,&$b) {
499 if($b['deleted'] || ($b['created'] !== $b['edited']))
503 * Post to Facebook stream
506 require_once('include/group.php');
508 logger('Facebook post');
513 $toplevel = (($b['id'] == $b['parent']) ? true : false);
516 $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1);
518 if((! $toplevel) && ($linking)) {
519 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
520 intval($b['parent']),
523 if(count($r) && substr($r[0]['uri'],0,4) === 'fb::')
524 $reply = substr($r[0]['uri'],4);
525 elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::')
526 $reply = substr($r[0]['extid'],4);
530 $u = q("SELECT * FROM user where uid = %d limit 1",
536 // only accept comments from the item owner. Other contacts are unknown to FB.
538 if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname']))
542 logger('facebook reply id=' . $reply);
545 if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) {
547 if($b['private'] && $reply === false) {
548 $allow_people = expand_acl($b['allow_cid']);
549 $allow_groups = expand_groups(expand_acl($b['allow_gid']));
550 $deny_people = expand_acl($b['deny_cid']);
551 $deny_groups = expand_groups(expand_acl($b['deny_gid']));
553 $recipients = array_unique(array_merge($allow_people,$allow_groups));
554 $deny = array_unique(array_merge($deny_people,$deny_groups));
556 $allow_str = dbesc(implode(', ',$recipients));
558 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'");
559 $allow_arr = array();
562 $allow_arr[] = $rr['notify'];
565 $deny_str = dbesc(implode(', ',$deny));
567 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'");
571 $deny_arr[] = $rr['notify'];
574 if(count($deny_arr) && (! count($allow_arr))) {
576 // One or more FB folks were denied access but nobody on FB was specifically allowed access.
577 // This might cause the post to be open to public on Facebook, but only to selected members
578 // on another network. Since this could potentially leak a post to somebody who was denied,
579 // we will skip posting it to Facebook with a slightly vague but relevant message that will
580 // hopefully lead somebody to this code comment for a better explanation of what went wrong.
582 notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL);
587 // if it's a private message but no Facebook members are allowed or denied, skip Facebook post
589 if((! count($allow_arr)) && (! count($deny_arr)))
593 if($b['verb'] == ACTIVITY_LIKE)
597 $appid = get_config('facebook', 'appid' );
598 $secret = get_config('facebook', 'appsecret' );
600 if($appid && $secret) {
602 logger('facebook: have appid+secret');
604 $fb_token = get_pconfig($b['uid'],'facebook','access_token');
607 // post to facebook if it's a public post and we've ticked the 'post to Facebook' box,
608 // or it's a private message with facebook participants
609 // or it's a reply or likes action to an existing facebook post
611 if($fb_token && ($toplevel || $b['private'] || $reply)) {
612 logger('facebook: able to post');
613 require_once('library/facebook.php');
614 require_once('include/bbcode.php');
618 logger('Facebook post: original msg=' . $msg, LOGGER_DATA);
620 // make links readable before we strip the code
622 // unless it's a dislike - just send the text as a comment
624 if($b['verb'] == ACTIVITY_DISLIKE)
625 $msg = trim(strip_tags(bbcode($msg)));
627 $search_str = $a->get_baseurl() . '/search';
629 if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
631 // don't use hashtags for message link
633 if(strpos($matches[2],$search_str) === false) {
635 if(substr($matches[2],0,5) != '[img]')
636 $linkname = $matches[2];
640 // strip tag links to avoid link clutter, this really should be
641 // configurable because we're losing information
643 $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
645 // provide the link separately for normal links
646 $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
648 if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
649 $image = $matches[1];
651 $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
653 if((strpos($link,z_root()) !== false) && (! $image))
654 $image = $a->get_baseurl() . '/images/friendica-64.jpg';
656 $msg = trim(strip_tags(bbcode($msg)));
657 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
659 // add any attachments as text urls
661 $arr = explode(',',$b['attach']);
665 foreach($arr as $r) {
667 $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
674 if (strlen($msg) > FACEBOOK_MAXPOSTLEN) {
676 require_once('library/slinky.php');
678 $display_url = $b['plink'];
680 $slinky = new Slinky( $display_url );
681 // setup a cascade of shortening services
682 // try to get a short link from these services
683 // in the order ur1.ca, trim, id.gd, tinyurl
684 $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
685 $shortlink = $slinky->short();
686 // the new message will be shortened such that "... $shortlink"
687 // will fit into the character limit
688 $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
689 $msg .= '... ' . $shortlink;
694 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
697 $postvars = array('access_token' => $fb_token);
701 'access_token' => $fb_token,
705 $postvars['picture'] = $image;
707 $postvars['link'] = $link;
709 $postvars['name'] = $linkname;
712 if(($b['private']) && ($toplevel)) {
713 $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
714 if(count($allow_arr))
715 $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
717 $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
718 $postvars['privacy'] .= '}';
723 $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
726 $url = 'https://graph.facebook.com/me/feed';
728 $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' . $b['plink'] . '"}';
731 logger('facebook: post to ' . $url);
732 logger('facebook: postvars: ' . print_r($postvars,true));
734 // "test_mode" prevents anything from actually being posted.
735 // Otherwise, let's do it.
737 if(! get_config('facebook','test_mode')) {
738 $x = post_url($url, $postvars);
740 $retj = json_decode($x);
742 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
743 dbesc('fb::' . $retj->id),
749 $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
750 require_once('include/queue_fn.php');
751 add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
752 notice( t('Facebook post failed. Queued for retry.') . EOL);
756 logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
764 function facebook_post_local(&$a,&$b) {
766 // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
767 // where we will discover it during background delivery.
769 // This can only be triggered by a local user posting to their own wall.
771 if((local_user()) && (local_user() == $b['uid'])) {
773 $fb_post = intval(get_pconfig(local_user(),'facebook','post'));
774 $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
776 // if API is used, default to the chosen settings
777 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
783 if(strlen($b['postopts']))
784 $b['postopts'] .= ',';
785 $b['postopts'] .= 'facebook';
790 function fb_queue_hook(&$a,&$b) {
792 $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
793 dbesc(NETWORK_FACEBOOK)
798 require_once('include/queue_fn.php');
801 if($x['network'] !== NETWORK_FACEBOOK)
804 logger('facebook_queue: run');
806 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid`
807 WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
815 $appid = get_config('facebook', 'appid' );
816 $secret = get_config('facebook', 'appsecret' );
818 if($appid && $secret) {
819 $fb_post = intval(get_pconfig($user['uid'],'facebook','post'));
820 $fb_token = get_pconfig($user['uid'],'facebook','access_token');
822 if($fb_post && $fb_token) {
823 logger('facebook_queue: able to post');
824 require_once('library/facebook.php');
826 $z = unserialize($x['content']);
828 $j = post_url($z['url'],$z['post']);
830 $retj = json_decode($j);
832 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
833 dbesc('fb::' . $retj->id),
836 logger('facebook_queue: success: ' . $j);
837 remove_queue_item($x['id']);
840 logger('facebook_queue: failed: ' . $j);
841 update_queue_time($x['id']);
848 function fb_consume_all($uid) {
850 require_once('include/items.php');
852 $access_token = get_pconfig($uid,'facebook','access_token');
856 if(! get_pconfig($uid,'facebook','no_wall')) {
857 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
858 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
860 $j = json_decode($s);
861 logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
862 fb_consume_stream($uid,$j,($private_wall) ? false : true);
865 $s = fetch_url('https://graph.facebook.com/me/home?access_token=' . $access_token);
867 $j = json_decode($s);
868 logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
869 fb_consume_stream($uid,$j,false);
874 function fb_get_photo($uid,$link) {
875 $access_token = get_pconfig($uid,'facebook','access_token');
876 if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
877 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
878 $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
880 $photo_id = $match[1];
881 $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
882 $j = json_decode($x);
884 return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
886 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
889 function fb_consume_stream($uid,$j,$wall = false) {
894 $user = q("SELECT `nickname`, `blockwall` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
900 $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
902 $no_linking = get_pconfig($uid,'facebook','no_linking');
906 $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
910 $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
911 $blocked_apps_arr = explode(',',$blocked_apps);
913 $self_id = get_pconfig($uid,'facebook','self_id');
914 if(! count($j->data) || (! strlen($self_id)))
917 foreach($j->data as $entry) {
918 logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
921 $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
922 dbesc('fb::' . $entry->id),
923 dbesc('fb::' . $entry->id),
929 $top_item = $r[0]['id'];
932 $post_exists = false;
937 $datarray['gravity'] = 0;
938 $datarray['uid'] = $uid;
939 $datarray['wall'] = (($wall) ? 1 : 0);
940 $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
941 $from = $entry->from;
942 if($from->id == $self_id)
943 $datarray['contact-id'] = $self[0]['id'];
945 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
950 $datarray['contact-id'] = $r[0]['id'];
953 // don't store post if we don't have a contact
955 if(! x($datarray,'contact-id')) {
956 logger('no contact: post ignored');
960 $datarray['verb'] = ACTIVITY_POST;
962 $datarray['owner-name'] = $self[0]['name'];
963 $datarray['owner-link'] = $self[0]['url'];
964 $datarray['owner-avatar'] = $self[0]['thumb'];
966 if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
967 $datarray['app'] = strip_tags($entry->application->name);
969 $datarray['app'] = 'facebook';
971 $found_blocked = false;
973 if(count($blocked_apps_arr)) {
974 foreach($blocked_apps_arr as $bad_appl) {
975 if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
976 $found_blocked = true;
982 logger('facebook: blocking application: ' . $datarray['app']);
986 $datarray['author-name'] = $from->name;
987 $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
988 $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
989 $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
991 $datarray['body'] = escape_tags($entry->message);
993 if($entry->picture && $entry->link) {
994 $datarray['body'] .= "\n\n" . '[url=' . $entry->link . '][img]' . $entry->picture . '[/img][/url]';
998 $datarray['body'] .= "\n\n" . '[img]' . $entry->picture . '[/img]';
999 // if just a link, it may be a wall photo - check
1001 $datarray['body'] .= fb_get_photo($uid,$entry->link);
1004 $datarray['body'] .= "\n" . $entry->name;
1006 $datarray['body'] .= "\n" . $entry->caption;
1007 if($entry->description)
1008 $datarray['body'] .= "\n" . $entry->description;
1009 $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1010 $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1012 // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1013 // as the identities are from a foreign system. Mark it as private to the owner.
1015 if($entry->privacy && $entry->privacy->value !== 'EVERYONE') {
1016 $datarray['private'] = 1;
1017 $datarray['allow_cid'] = '<' . $uid . '>';
1020 $top_item = item_store($datarray);
1021 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1027 logger('fb: new top level item posted');
1031 if(isset($entry->likes) && isset($entry->likes->data))
1032 $likers = $entry->likes->data;
1036 if(isset($entry->comments) && isset($entry->comments->data))
1037 $comments = $entry->comments->data;
1041 if(is_array($likers)) {
1042 foreach($likers as $likes) {
1047 // If we posted the like locally, it will be found with our url, not the FB url.
1049 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id);
1051 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s'
1052 AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1053 dbesc($orig_post['uri']),
1055 dbesc(ACTIVITY_LIKE),
1056 dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1063 $likedata = array();
1064 $likedata['parent'] = $top_item;
1065 $likedata['verb'] = ACTIVITY_LIKE;
1066 $likedata['gravity'] = 3;
1067 $likedata['uid'] = $uid;
1068 $likedata['wall'] = (($wall) ? 1 : 0);
1069 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1070 $likedata['parent-uri'] = $orig_post['uri'];
1071 if($likes->id == $self_id)
1072 $likedata['contact-id'] = $self[0]['id'];
1074 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1079 $likedata['contact-id'] = $r[0]['id'];
1081 if(! x($likedata,'contact-id'))
1082 $likedata['contact-id'] = $orig_post['contact-id'];
1084 $likedata['app'] = 'facebook';
1085 $likedata['verb'] = ACTIVITY_LIKE;
1086 $likedata['author-name'] = $likes->name;
1087 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1088 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1090 $author = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1091 $objauthor = '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1092 $post_type = t('status');
1093 $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1094 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1096 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1097 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' .
1098 '<id>' . $orig_post['uri'] . '</id><link>' . xmlify('<link rel="alternate" type="text/html" href="' . xmlify($orig_post['plink']) . '" />') . '</link><title>' . $orig_post['title'] . '</title><content>' . $orig_post['body'] . '</content></object>';
1100 $item = item_store($likedata);
1103 if(is_array($comments)) {
1104 foreach($comments as $cmnt) {
1109 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1111 dbesc('fb::' . $cmnt->id),
1112 dbesc('fb::' . $cmnt->id)
1117 $cmntdata = array();
1118 $cmntdata['parent'] = $top_item;
1119 $cmntdata['verb'] = ACTIVITY_POST;
1120 $cmntdata['gravity'] = 6;
1121 $cmntdata['uid'] = $uid;
1122 $cmntdata['wall'] = (($wall) ? 1 : 0);
1123 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1124 $cmntdata['parent-uri'] = $orig_post['uri'];
1125 if($cmnt->from->id == $self_id) {
1126 $cmntdata['contact-id'] = $self[0]['id'];
1129 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1130 dbesc($cmnt->from->id),
1134 $cmntdata['contact-id'] = $r[0]['id'];
1135 if($r[0]['blocked'] || $r[0]['readonly'])
1139 if(! x($cmntdata,'contact-id'))
1140 $cmntdata['contact-id'] = $orig_post['contact-id'];
1142 $cmntdata['app'] = 'facebook';
1143 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1144 $cmntdata['edited'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1145 $cmntdata['verb'] = ACTIVITY_POST;
1146 $cmntdata['author-name'] = $cmnt->from->name;
1147 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1148 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1149 $cmntdata['body'] = $cmnt->message;
1150 $item = item_store($cmntdata);