3 * Name: Facebook Connector
5 * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
6 * Tobias Hößl <https://github.com/CatoTH/>
10 * Installing the Friendica/Facebook connector
12 * 1. register an API key for your site from developer.facebook.com
13 * a. We'd be very happy if you include "Friendica" in the application name
14 * to increase name recognition. The Friendica icons are also present
15 * in the images directory and may be uploaded as a Facebook app icon.
16 * Use images/friendica-16.jpg for the Icon and images/friendica-128.jpg for the Logo.
17 * b. The url should be your site URL with a trailing slash.
18 * Friendica is a software application and does not require a Privacy Policy
19 * or Terms of Service, though your installation of it might. Facebook may require
20 * that you provide a Privacy Policy, which we find ironic.
21 * c. Set the following values in your .htconfig.php file
22 * $a->config['facebook']['appid'] = 'xxxxxxxxxxx';
23 * $a->config['facebook']['appsecret'] = 'xxxxxxxxxxxxxxx';
24 * Replace with the settings Facebook gives you.
25 * d. Navigate to Set Web->Site URL & Domain -> Website Settings. Set
26 * Site URL to yoursubdomain.yourdomain.com. Set Site Domain to your
28 * 2. Visit the Facebook Settings section of the "Settings->Plugin Settings" page.
29 * and click 'Install Facebook Connector'.
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. Optional step: If you want to use Facebook Real Time Updates (so new messages
35 * and new contacts are added ~1min after they are postet / added on FB), go to
36 * Settings -> plugins -> facebook and press the "Activate Real-Time Updates"-button.
37 * 6. You're done. To turn it off visit the Plugin Settings page again and
38 * 'Remove Facebook posting'.
40 * Vidoes and embeds will not be posted if there is no other content. Links
41 * and images will be converted to a format suitable for the Facebook API and
42 * long posts truncated - with a link to view the full post.
44 * Facebook contacts will not be able to view private photos, as they are not able to
45 * authenticate to your site to establish identity. We will address this
46 * in a future release.
50 * - Implement a method for the administrator to delete all configuration data the plugin has created,
51 * e.g. the app_access_token
54 // Size of maximum post length increased
55 // see http://www.facebook.com/schrep/posts/203969696349811
56 // define('FACEBOOK_MAXPOSTLEN', 420);
57 define('FACEBOOK_MAXPOSTLEN', 63206);
58 define('FACEBOOK_SESSION_ERR_NOTIFICATION_INTERVAL', 259200); // 3 days
59 define('FACEBOOK_DEFAULT_POLL_INTERVAL', 60); // given in minutes
60 define('FACEBOOK_MIN_POLL_INTERVAL', 5);
62 require_once('include/security.php');
64 function facebook_install() {
65 register_hook('post_local', 'addon/facebook/facebook.php', 'facebook_post_local');
66 register_hook('notifier_normal', 'addon/facebook/facebook.php', 'facebook_post_hook');
67 register_hook('jot_networks', 'addon/facebook/facebook.php', 'facebook_jot_nets');
68 register_hook('connector_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings');
69 register_hook('cron', 'addon/facebook/facebook.php', 'facebook_cron');
70 register_hook('enotify', 'addon/facebook/facebook.php', 'facebook_enotify');
71 register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
75 function facebook_uninstall() {
76 unregister_hook('post_local', 'addon/facebook/facebook.php', 'facebook_post_local');
77 unregister_hook('notifier_normal', 'addon/facebook/facebook.php', 'facebook_post_hook');
78 unregister_hook('jot_networks', 'addon/facebook/facebook.php', 'facebook_jot_nets');
79 unregister_hook('connector_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings');
80 unregister_hook('cron', 'addon/facebook/facebook.php', 'facebook_cron');
81 unregister_hook('enotify', 'addon/facebook/facebook.php', 'facebook_enotify');
82 unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
85 unregister_hook('post_local_end', 'addon/facebook/facebook.php', 'facebook_post_hook');
86 unregister_hook('plugin_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings');
90 /* declare the facebook_module function so that /facebook url requests will land here */
92 function facebook_module() {}
96 // If a->argv[1] is a nickname, this is a callback from Facebook oauth requests.
97 // If $_REQUEST["realtime_cb"] is set, this is a callback from the Real-Time Updates API
99 function facebook_init(&$a) {
101 if (x($_REQUEST, "realtime_cb") && x($_REQUEST, "realtime_cb")) {
102 logger("facebook_init: Facebook Real-Time callback called", LOGGER_DEBUG);
104 if (x($_REQUEST, "hub_verify_token")) {
105 // this is the verification callback while registering for real time updates
107 $verify_token = get_config('facebook', 'cb_verify_token');
108 if ($verify_token != $_REQUEST["hub_verify_token"]) {
109 logger('facebook_init: Wrong Facebook Callback Verifier - expected ' . $verify_token . ', got ' . $_REQUEST["hub_verify_token"]);
113 if (x($_REQUEST, "hub_challenge")) {
114 logger('facebook_init: Answering Challenge: ' . $_REQUEST["hub_challenge"], LOGGER_DATA);
115 echo $_REQUEST["hub_challenge"];
120 require_once('include/items.php');
122 // this is a status update
123 $content = file_get_contents("php://input");
124 if (is_numeric($content)) $content = file_get_contents("php://input");
125 $js = json_decode($content);
126 logger(print_r($js, true), LOGGER_DATA);
128 if (!isset($js->object) || $js->object != "user" || !isset($js->entry)) {
129 logger('facebook_init: Could not parse Real-Time Update data', LOGGER_DEBUG);
133 $affected_users = array("feed" => array(), "friends" => array());
135 foreach ($js->entry as $entry) {
136 $fbuser = $entry->uid;
137 foreach ($entry->changed_fields as $field) {
138 if (!isset($affected_users[$field])) {
139 logger('facebook_init: Unknown field "' . $field . '"');
142 if (in_array($fbuser, $affected_users[$field])) continue;
144 $r = q("SELECT `uid` FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'self_id' AND `v` = '%s' LIMIT 1", dbesc($fbuser));
149 $access_token = get_pconfig($uid,'facebook','access_token');
155 logger('facebook_init: FB-User ' . $fbuser . ' / feed', LOGGER_DEBUG);
157 if(! get_pconfig($uid,'facebook','no_wall')) {
158 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
159 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
161 $j = json_decode($s);
162 if (isset($j->data)) {
163 logger('facebook_init: wall: ' . print_r($j,true), LOGGER_DATA);
164 fb_consume_stream($uid,$j,($private_wall) ? false : true);
166 logger('facebook_init: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
173 logger('facebook_init: FB-User ' . $fbuser . ' / friends', LOGGER_DEBUG);
175 fb_get_friends($uid, false);
176 set_pconfig($uid,'facebook','friend_check',time());
179 logger('facebook_init: Unknown callback field for ' . $fbuser, LOGGER_NORMAL);
181 $affected_users[$field][] = $fbuser;
191 $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1",
198 $auth_code = (x($_GET, 'code') ? $_GET['code'] : '');
199 $error = (x($_GET, 'error_description') ? $_GET['error_description'] : '');
203 logger('facebook_init: Error: ' . $error);
205 if($auth_code && $uid) {
207 $appid = get_config('facebook','appid');
208 $appsecret = get_config('facebook', 'appsecret');
210 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id='
211 . $appid . '&client_secret=' . $appsecret . '&redirect_uri='
212 . urlencode($a->get_baseurl() . '/facebook/' . $nick)
213 . '&code=' . $auth_code);
215 logger('facebook_init: returned access token: ' . $x, LOGGER_DATA);
217 if(strpos($x,'access_token=') !== false) {
218 $token = str_replace('access_token=', '', $x);
219 if(strpos($token,'&') !== false)
220 $token = substr($token,0,strpos($token,'&'));
221 set_pconfig($uid,'facebook','access_token',$token);
222 set_pconfig($uid,'facebook','post','1');
223 if(get_pconfig($uid,'facebook','no_linking') === false)
224 set_pconfig($uid,'facebook','no_linking',1);
226 fb_get_friends($uid, true);
227 fb_consume_all($uid);
236 function fb_get_self($uid) {
237 $access_token = get_pconfig($uid,'facebook','access_token');
240 $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token);
242 $j = json_decode($s);
243 set_pconfig($uid,'facebook','self_id',(string) $j->id);
247 function fb_get_friends_sync_new($uid, $access_token, $person) {
248 $link = 'http://facebook.com/profile.php?id=' . $person->id;
250 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
255 if (count($r) == 0) {
256 logger('fb_get_friends: new contact found: ' . $link, LOGGER_DEBUG);
258 fb_get_friends_sync_full($uid, $access_token, $person);
262 function fb_get_friends_sync_full($uid, $access_token, $person) {
263 $s = fetch_url('https://graph.facebook.com/' . $person->id . '?access_token=' . $access_token);
265 $jp = json_decode($s);
266 logger('fb_get_friends: info: ' . print_r($jp,true), LOGGER_DATA);
268 // always use numeric link for consistency
270 $jp->link = 'http://facebook.com/profile.php?id=' . $person->id;
272 // If its a page then set the first name from the username
273 if (!$jp->first_name and $jp->username)
274 $jp->first_name = $jp->username;
276 // check if we already have a contact
278 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
285 // check that we have all the photos, this has been known to fail on occasion
287 if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro'])) {
288 require_once("Photo.php");
290 $photos = import_profile_photo('https://graph.facebook.com/' . $jp->id . '/picture', $uid, $r[0]['id']);
292 $r = q("UPDATE `contact` SET `photo` = '%s',
298 WHERE `id` = %d LIMIT 1
303 dbesc(datetime_convert()),
304 dbesc(datetime_convert()),
305 dbesc(datetime_convert()),
313 // create contact record
314 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
315 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
316 `writable`, `blocked`, `readonly`, `pending` )
317 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ",
319 dbesc(datetime_convert()),
321 dbesc(normalise_link($jp->link)),
325 dbesc('facebook ' . $jp->id),
327 dbesc(($jp->nickname) ? $jp->nickname : strtolower($jp->first_name)),
328 dbesc('https://graph.facebook.com/' . $jp->id . '/picture'),
329 dbesc(NETWORK_FACEBOOK),
330 intval(CONTACT_IS_FRIEND),
336 $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1",
346 $contact_id = $r[0]['id'];
348 require_once("Photo.php");
350 $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id);
352 $r = q("UPDATE `contact` SET `photo` = '%s',
358 WHERE `id` = %d LIMIT 1
363 dbesc(datetime_convert()),
364 dbesc(datetime_convert()),
365 dbesc(datetime_convert()),
372 // if $fullsync is true, only new contacts are searched for
374 function fb_get_friends($uid, $fullsync = true) {
376 $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
382 $access_token = get_pconfig($uid,'facebook','access_token');
384 $no_linking = get_pconfig($uid,'facebook','no_linking');
390 $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token);
392 logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA);
393 $j = json_decode($s);
394 logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA);
397 foreach($j->data as $person)
399 fb_get_friends_sync_full($uid, $access_token, $person);
401 fb_get_friends_sync_new($uid, $access_token, $person);
405 // This is the POST method to the facebook settings page
406 // Content is posted to Facebook in the function facebook_post_hook()
408 function facebook_post(&$a) {
413 $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0);
414 set_pconfig($uid,'facebook','post_by_default', $value);
416 $no_linking = get_pconfig($uid,'facebook','no_linking');
418 $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0);
419 set_pconfig($uid,'facebook','no_wall',$no_wall);
421 $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0);
422 set_pconfig($uid,'facebook','private_wall',$private_wall);
425 set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps'])));
427 $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0);
428 set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1));
430 // FB linkage was allowed but has just been turned off - remove all FB contacts and posts
432 if((! intval($no_linking)) && (! intval($linkvalue))) {
433 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ",
435 dbesc(NETWORK_FACEBOOK)
438 require_once('include/Contact.php');
440 contact_remove($rr['id']);
443 elseif(intval($no_linking) && intval($linkvalue)) {
444 // FB linkage is now allowed - import stuff.
446 fb_get_friends($uid, true);
447 fb_consume_all($uid);
450 info( t('Settings updated.') . EOL);
456 // Facebook settings form
458 function facebook_content(&$a) {
461 notice( t('Permission denied.') . EOL);
465 if($a->argc > 1 && $a->argv[1] === 'remove') {
466 del_pconfig(local_user(),'facebook','post');
467 info( t('Facebook disabled') . EOL);
470 if($a->argc > 1 && $a->argv[1] === 'friends') {
471 fb_get_friends(local_user(), true);
472 info( t('Updating contacts') . EOL);
477 $fb_installed = false;
478 if (get_pconfig(local_user(),'facebook','post')) {
479 $access_token = get_pconfig(local_user(),'facebook','access_token');
481 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
482 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
484 $j = json_decode($s);
485 if (isset($j->data)) $fb_installed = true;
490 $appid = get_config('facebook','appid');
493 notice( t('Facebook API key is missing.') . EOL);
497 $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="'
498 . $a->get_baseurl() . '/addon/facebook/facebook.css' . '" media="all" />' . "\r\n";
500 $o .= '<h3>' . t('Facebook Connect') . '</h3>';
502 if(! $fb_installed) {
503 $o .= '<div id="facebook-enable-wrapper">';
505 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri='
506 . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Install Facebook connector for this account.') . '</a>';
511 $o .= '<div id="facebook-disable-wrapper">';
513 $o .= '<a href="' . $a->get_baseurl() . '/facebook/remove' . '">' . t('Remove Facebook connector') . '</a></div>';
515 $o .= '<div id="facebook-enable-wrapper">';
517 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri='
518 . $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>';
521 $o .= '<div id="facebook-post-default-form">';
522 $o .= '<form action="facebook" method="post" >';
523 $post_by_default = get_pconfig(local_user(),'facebook','post_by_default');
524 $checked = (($post_by_default) ? ' checked="checked" ' : '');
525 $o .= '<input type="checkbox" name="post_by_default" value="1"' . $checked . '/>' . ' ' . t('Post to Facebook by default') . EOL;
527 $no_linking = get_pconfig(local_user(),'facebook','no_linking');
528 $checked = (($no_linking) ? '' : ' checked="checked" ');
529 $o .= '<input type="checkbox" name="facebook_linking" value="1"' . $checked . '/>' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ;
531 $o .= '<p>' . t('Facebook conversations consist of your <em>profile wall</em> and your friend <em>stream</em>.');
532 $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.');
533 $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '</p>';
535 $private_wall = get_pconfig(local_user(),'facebook','private_wall');
536 $checked = (($private_wall) ? ' checked="checked" ' : '');
537 $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 ;
540 $no_wall = get_pconfig(local_user(),'facebook','no_wall');
541 $checked = (($no_wall) ? ' checked="checked" ' : '');
542 $o .= '<input type="checkbox" name="facebook_no_wall" value="1"' . $checked . '/>' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ;
544 $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>';
547 $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps');
549 $o .= '<div><label id="blocked-apps-label" for="blocked-apps">' . t('Comma separated applications to ignore') . ' </label></div>';
550 $o .= '<div><textarea id="blocked-apps" name="blocked_apps" >' . htmlspecialchars($blocked_apps) . '</textarea></div>';
552 $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form></div>';
560 function facebook_cron($a,$b) {
561 //del_config('facebook', 'realtime_active');
562 //del_config('facebook', 'realtime_err_mailsent');
563 //del_config('facebook', 'cb_verify_token');
565 $last = get_config('facebook','last_poll');
567 $poll_interval = intval(get_config('facebook','poll_interval'));
569 $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL;
572 $next = $last + $poll_interval;
577 logger('facebook_cron');
580 // Find the FB users on this site and randomize in case one of them
581 // uses an obscene amount of memory. It may kill this queue run
582 // but hopefully we'll get a few others through on each run.
584 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() ");
587 if(get_pconfig($rr['uid'],'facebook','no_linking'))
589 $ab = intval(get_config('system','account_abandon_days'));
591 $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1",
599 // check for new friends once a day
600 $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check');
601 if($last_friend_check)
602 $next_friend_check = $last_friend_check + 86400;
603 if($next_friend_check <= time()) {
604 fb_get_friends($rr['uid'], true);
605 set_pconfig($rr['uid'],'facebook','friend_check',time());
607 fb_consume_all($rr['uid']);
611 if (get_config('facebook', 'realtime_active') == 1) {
612 if (!facebook_check_realtime_active()) {
614 logger('facebook_cron: Facebook is not sending Real-Time Updates any more, although it is supposed to. Trying to fix it...', LOGGER_NORMAL);
615 facebook_subscription_add_users();
617 if (facebook_check_realtime_active())
618 logger('facebook_cron: Successful', LOGGER_NORMAL);
620 logger('facebook_cron: Failed', LOGGER_NORMAL);
622 if(strlen($a->config['admin_email']) && !get_config('facebook', 'realtime_err_mailsent')) {
623 $res = mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'),
624 "Hi!\n\nThere's a problem with the Facebook Real-Time Updates that cannot be solved automatically. Maybe a permission issue?\n\nPlease try to re-activate it on " . $a->config["system"]["url"] . "/admin/plugins/facebook\n\nThis e-mail will only be sent once.",
625 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n"
626 . 'Content-type: text/plain; charset=UTF-8' . "\n"
627 . 'Content-transfer-encoding: 8bit'
630 set_config('facebook', 'realtime_err_mailsent', 1);
633 } else { // !facebook_check_realtime_active()
634 del_config('facebook', 'realtime_err_mailsent');
638 set_config('facebook','last_poll', time());
644 function facebook_plugin_settings(&$a,&$b) {
646 $b .= '<div class="settings-block">';
647 $b .= '<h3>' . t('Facebook') . '</h3>';
648 $b .= '<a href="facebook">' . t('Facebook Connector Settings') . '</a><br />';
654 function facebook_plugin_admin(&$a, &$o){
657 $o = '<input type="hidden" name="form_security_token" value="' . get_form_security_token("fbsave") . '">';
659 $o .= '<h4>' . t('Facebook API Key') . '</h4>';
661 $appid = get_config('facebook', 'appid' );
662 $appsecret = get_config('facebook', 'appsecret' );
663 $poll_interval = get_config('facebook', 'poll_interval' );
664 if (!$poll_interval) $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL;
666 $ret1 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appid' LIMIT 1");
667 $ret2 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appsecret' LIMIT 1");
668 if ((count($ret1) > 0 && $ret1[0]['v'] != $appid) || (count($ret2) > 0 && $ret2[0]['v'] != $appsecret)) $o .= t('Error: it appears that you have specified the App-ID and -Secret in your .htconfig.php file. As long as they are specified there, they cannot be set using this form.<br><br>');
670 $working_connection = false;
671 if ($appid && $appsecret) {
672 $subs = facebook_subscriptions_get();
673 if ($subs === null) $o .= t('Error: the given API Key seems to be incorrect (the application access token could not be retrieved).') . '<br>';
674 elseif (is_array($subs)) {
675 $o .= t('The given API Key seems to work correctly.') . '<br>';
676 $working_connection = true;
677 } else $o .= t('The correctness of the API Key could not be detected. Somthing strange\'s going on.') . '<br>';
680 $o .= '<label for="fb_appid">' . t('App-ID / API-Key') . '</label><input name="appid" type="text" value="' . escape_tags($appid ? $appid : "") . '"><br style="clear: both;">';
681 $o .= '<label for="fb_appsecret">' . t('Application secret') . '</label><input name="appsecret" type="text" value="' . escape_tags($appsecret ? $appsecret : "") . '"><br style="clear: both;">';
682 $o .= '<label for="fb_poll_interval">' . sprintf(t('Polling Interval (min. %1$s minutes)'), FACEBOOK_MIN_POLL_INTERVAL) . '</label><input name="poll_interval" type="number" min="' . FACEBOOK_MIN_POLL_INTERVAL . '" value="' . $poll_interval . '"><br style="clear: both;">';
683 $o .= '<input type="submit" name="fb_save_keys" value="' . t('Save') . '">';
685 if ($working_connection) {
686 $o .= '<h4>' . t('Real-Time Updates') . '</h4>';
688 $activated = facebook_check_realtime_active();
690 $o .= t('Real-Time Updates are activated.') . '<br><br>';
691 $o .= '<input type="submit" name="real_time_deactivate" value="' . t('Deactivate Real-Time Updates') . '">';
693 $o .= t('Real-Time Updates not activated.') . '<br><input type="submit" name="real_time_activate" value="' . t('Activate Real-Time Updates') . '">';
698 function facebook_plugin_admin_post(&$a, &$o){
699 check_form_security_token_redirectOnErr('/admin/plugins/facebook', 'fbsave');
701 if (x($_REQUEST,'fb_save_keys')) {
702 set_config('facebook', 'appid', $_REQUEST['appid']);
703 set_config('facebook', 'appsecret', $_REQUEST['appsecret']);
704 $poll_interval = IntVal($_REQUEST['poll_interval']);
705 if ($poll_interval >= FACEBOOK_MIN_POLL_INTERVAL) set_config('facebook', 'poll_interval', $poll_interval);
706 del_config('facebook', 'app_access_token');
707 info(t('The new values have been saved.'));
709 if (x($_REQUEST,'real_time_activate')) {
710 facebook_subscription_add_users();
712 if (x($_REQUEST,'real_time_deactivate')) {
713 facebook_subscription_del_users();
717 function facebook_jot_nets(&$a,&$b) {
721 $fb_post = get_pconfig(local_user(),'facebook','post');
722 if(intval($fb_post) == 1) {
723 $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default');
724 $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : '');
725 $b .= '<div class="profile-jot-net"><input type="checkbox" name="facebook_enable"' . $selected . ' value="1" /> '
726 . t('Post to Facebook') . '</div>';
731 function facebook_post_hook(&$a,&$b) {
734 if($b['deleted'] || ($b['created'] !== $b['edited']))
738 * Post to Facebook stream
741 require_once('include/group.php');
742 require_once('include/html2plain.php');
744 logger('Facebook post');
749 $toplevel = (($b['id'] == $b['parent']) ? true : false);
752 $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1);
754 if((! $toplevel) && ($linking)) {
755 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
756 intval($b['parent']),
759 if(count($r) && substr($r[0]['uri'],0,4) === 'fb::')
760 $reply = substr($r[0]['uri'],4);
761 elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::')
762 $reply = substr($r[0]['extid'],4);
766 $u = q("SELECT * FROM user where uid = %d limit 1",
772 // only accept comments from the item owner. Other contacts are unknown to FB.
774 if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname']))
778 logger('facebook reply id=' . $reply);
781 if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) {
783 if($b['private'] && $reply === false) {
784 $allow_people = expand_acl($b['allow_cid']);
785 $allow_groups = expand_groups(expand_acl($b['allow_gid']));
786 $deny_people = expand_acl($b['deny_cid']);
787 $deny_groups = expand_groups(expand_acl($b['deny_gid']));
789 $recipients = array_unique(array_merge($allow_people,$allow_groups));
790 $deny = array_unique(array_merge($deny_people,$deny_groups));
792 $allow_str = dbesc(implode(', ',$recipients));
794 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'");
795 $allow_arr = array();
798 $allow_arr[] = $rr['notify'];
801 $deny_str = dbesc(implode(', ',$deny));
803 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'");
807 $deny_arr[] = $rr['notify'];
810 if(count($deny_arr) && (! count($allow_arr))) {
812 // One or more FB folks were denied access but nobody on FB was specifically allowed access.
813 // This might cause the post to be open to public on Facebook, but only to selected members
814 // on another network. Since this could potentially leak a post to somebody who was denied,
815 // we will skip posting it to Facebook with a slightly vague but relevant message that will
816 // hopefully lead somebody to this code comment for a better explanation of what went wrong.
818 notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL);
823 // if it's a private message but no Facebook members are allowed or denied, skip Facebook post
825 if((! count($allow_arr)) && (! count($deny_arr)))
829 if($b['verb'] == ACTIVITY_LIKE)
833 $appid = get_config('facebook', 'appid' );
834 $secret = get_config('facebook', 'appsecret' );
836 if($appid && $secret) {
838 logger('facebook: have appid+secret');
840 $fb_token = get_pconfig($b['uid'],'facebook','access_token');
843 // post to facebook if it's a public post and we've ticked the 'post to Facebook' box,
844 // or it's a private message with facebook participants
845 // or it's a reply or likes action to an existing facebook post
847 if($fb_token && ($toplevel || $b['private'] || $reply)) {
848 logger('facebook: able to post');
849 require_once('library/facebook.php');
850 require_once('include/bbcode.php');
854 logger('Facebook post: original msg=' . $msg, LOGGER_DATA);
856 // make links readable before we strip the code
858 // unless it's a dislike - just send the text as a comment
860 if($b['verb'] == ACTIVITY_DISLIKE)
861 $msg = trim(strip_tags(bbcode($msg)));
864 /*$search_str = $a->get_baseurl() . '/search';
866 if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
868 // don't use hashtags for message link
870 if(strpos($matches[2],$search_str) === false) {
872 if(substr($matches[2],0,5) != '[img]')
873 $linkname = $matches[2];
877 // strip tag links to avoid link clutter, this really should be
878 // configurable because we're losing information
880 $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
882 // provide the link separately for normal links
883 $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
885 if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
886 $image = $matches[1];
888 $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
890 if((strpos($link,z_root()) !== false) && (! $image))
891 $image = $a->get_baseurl() . '/images/friendica-64.jpg';
893 $msg = trim(strip_tags(bbcode($msg)));*/
897 // Looking for the first image
899 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches))
900 $image = $matches[3];
903 if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches))
904 $image = $matches[1];
906 // Checking for a bookmark element
908 if (strpos($body, "[bookmark") !== false) {
909 // splitting the text in two parts:
910 // before and after the bookmark
911 $pos = strpos($body, "[bookmark");
912 $body1 = substr($body, 0, $pos);
913 $body2 = substr($body, $pos);
915 // Removing the bookmark and all quotes after the bookmark
916 // they are mostly only the content after the bookmark.
917 $body2 = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism",'',$body2);
918 $body2 = preg_replace("/\[quote\=([^\]]*)\](.*?)\[\/quote\]/ism",'',$body2);
919 $body2 = preg_replace("/\[quote\](.*?)\[\/quote\]/ism",'',$body2);
921 $body = $body1.$body2;
924 // At first convert the text to html
925 $html = bbcode($body);
927 // Then convert it to plain text
928 $msg = trim($b['title']." \n\n".html2plain($html, 0, true));
929 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
931 // Removing multiple newlines
932 while (strpos($msg, "\n\n\n") !== false)
933 $msg = str_replace("\n\n\n", "\n\n", $msg);
935 // add any attachments as text urls
936 $arr = explode(',',$b['attach']);
940 foreach($arr as $r) {
942 $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
944 $msg .= "\n".$matches[1];
951 // look for bookmark-bbcode and handle it with priority
952 if(preg_match("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/is",$b['body'],$matches)) {
954 $linkname = $matches[2];
957 // If there is no bookmark element then take the first link
959 $links = collecturls($html);
960 if (sizeof($links) > 0) {
962 $link = current($links);
966 // Remove trailing and leading spaces
969 // Since facebook increased the maxpostlen massively this never should happen again :)
970 if (strlen($msg) > FACEBOOK_MAXPOSTLEN) {
972 require_once('library/slinky.php');
974 $display_url = $b['plink'];
976 $slinky = new Slinky( $display_url );
977 // setup a cascade of shortening services
978 // try to get a short link from these services
979 // in the order ur1.ca, trim, id.gd, tinyurl
980 $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
981 $shortlink = $slinky->short();
982 // the new message will be shortened such that "... $shortlink"
983 // will fit into the character limit
984 $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
985 $msg .= '... ' . $shortlink;
988 // Fallback - if message is empty
998 // If there is nothing to post then exit
1002 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
1005 $postvars = array('access_token' => $fb_token);
1009 'access_token' => $fb_token,
1013 $postvars['picture'] = $image;
1014 //$postvars['type'] = "photo";
1017 $postvars['link'] = $link;
1018 //$postvars['type'] = "link";
1020 if(isset($linkname))
1021 $postvars['name'] = $linkname;
1024 if(($b['private']) && ($toplevel)) {
1025 $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
1026 if(count($allow_arr))
1027 $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
1028 if(count($deny_arr))
1029 $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
1030 $postvars['privacy'] .= '}';
1035 $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
1036 } else if (($link != "") or ($image != "") or ($b['title'] == '') or (strlen($msg) < 500)) {
1037 $url = 'https://graph.facebook.com/me/feed';
1039 $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' . $b['plink'] . '"}';
1041 // if its only a message and a subject and the message is larger than 500 characters then post it as note
1043 'access_token' => $fb_token,
1044 'message' => bbcode($b['body']),
1045 'subject' => $b['title'],
1047 $url = 'https://graph.facebook.com/me/notes';
1050 logger('facebook: post to ' . $url);
1051 logger('facebook: postvars: ' . print_r($postvars,true));
1053 // "test_mode" prevents anything from actually being posted.
1054 // Otherwise, let's do it.
1056 if(! get_config('facebook','test_mode')) {
1057 $x = post_url($url, $postvars);
1058 logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
1060 $retj = json_decode($x);
1062 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
1063 dbesc('fb::' . $retj->id),
1069 $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
1070 require_once('include/queue_fn.php');
1071 add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
1072 notice( t('Facebook post failed. Queued for retry.') . EOL);
1075 if (isset($retj->error) && $retj->error->type == "OAuthException" && $retj->error->code == 190) {
1076 logger('Facebook session has expired due to changed password.', LOGGER_DEBUG);
1078 $last_notification = get_pconfig($b['uid'], 'facebook', 'session_expired_mailsent');
1079 if (!$last_notification || $last_notification < (time() - FACEBOOK_SESSION_ERR_NOTIFICATION_INTERVAL)) {
1080 require_once('include/enotify.php');
1082 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($b['uid']) );
1085 'type' => NOTIFY_SYSTEM,
1086 'system_type' => 'facebook_connection_invalid',
1087 'language' => $r[0]['language'],
1088 'to_name' => $r[0]['username'],
1089 'to_email' => $r[0]['email'],
1090 'source_name' => t('Administrator'),
1091 'source_link' => $a->config["system"]["url"],
1092 'source_photo' => $a->config["system"]["url"] . '/images/person-80.jpg',
1095 set_pconfig($b['uid'], 'facebook', 'session_expired_mailsent', time());
1096 } else logger('Facebook: No notification, as the last one was sent on ' . $last_notification, LOGGER_DEBUG);
1105 function facebook_enotify(&$app, &$data) {
1106 if (x($data, 'params') && $data['params']['type'] == NOTIFY_SYSTEM && x($data['params'], 'system_type') && $data['params']['system_type'] == 'facebook_connection_invalid') {
1107 $data['itemlink'] = '/facebook';
1108 $data['epreamble'] = $data['preamble'] = t('Your Facebook connection became invalid. Please Re-authenticate.');
1109 $data['subject'] = t('Facebook connection became invalid');
1110 $data['body'] = sprintf( t("Hi %1\$s,\n\nThe connection between your accounts on %2\$s and Facebook became invalid. This usually happens after you change your Facebook-password. To enable the connection again, you have to %3\$sre-authenticate the Facebook-connector%4\$s."), $data['params']['to_name'], "[url=" . $app->config["system"]["url"] . "]" . $app->config["sitename"] . "[/url]", "[url=" . $app->config["system"]["url"] . "/facebook]", "[/url]");
1114 function facebook_post_local(&$a,&$b) {
1116 // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
1117 // where we will discover it during background delivery.
1119 // This can only be triggered by a local user posting to their own wall.
1121 if((local_user()) && (local_user() == $b['uid'])) {
1123 $fb_post = intval(get_pconfig(local_user(),'facebook','post'));
1124 $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
1126 // if API is used, default to the chosen settings
1127 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
1133 if(strlen($b['postopts']))
1134 $b['postopts'] .= ',';
1135 $b['postopts'] .= 'facebook';
1140 function fb_queue_hook(&$a,&$b) {
1142 $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
1143 dbesc(NETWORK_FACEBOOK)
1148 require_once('include/queue_fn.php');
1150 foreach($qi as $x) {
1151 if($x['network'] !== NETWORK_FACEBOOK)
1154 logger('facebook_queue: run');
1156 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid`
1157 WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
1165 $appid = get_config('facebook', 'appid' );
1166 $secret = get_config('facebook', 'appsecret' );
1168 if($appid && $secret) {
1169 $fb_post = intval(get_pconfig($user['uid'],'facebook','post'));
1170 $fb_token = get_pconfig($user['uid'],'facebook','access_token');
1172 if($fb_post && $fb_token) {
1173 logger('facebook_queue: able to post');
1174 require_once('library/facebook.php');
1176 $z = unserialize($x['content']);
1178 $j = post_url($z['url'],$z['post']);
1180 $retj = json_decode($j);
1182 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
1183 dbesc('fb::' . $retj->id),
1186 logger('facebook_queue: success: ' . $j);
1187 remove_queue_item($x['id']);
1190 logger('facebook_queue: failed: ' . $j);
1191 update_queue_time($x['id']);
1198 function fb_get_timeline($access_token, &$since) {
1200 $entries->data = array();
1203 $url = 'https://graph.facebook.com/me/home?access_token='.$access_token;
1206 $url .= "&since=".$since;
1209 $s = fetch_url($url);
1210 $j = json_decode($s);
1211 $oldestdate = time();
1212 if (isset($j->data))
1213 foreach ($j->data as $entry) {
1214 $created = strtotime($entry->created_time);
1216 if ($newest < $created)
1219 if ($created >= $since)
1220 $entries->data[] = $entry;
1222 if ($created <= $oldestdate)
1223 $oldestdate = $created;
1228 $url = $j->paging->next;
1230 } while (($oldestdate > $since) and ($since != 0) and ($url != ''));
1232 if ($newest > $since)
1238 function fb_consume_all($uid) {
1240 require_once('include/items.php');
1242 $access_token = get_pconfig($uid,'facebook','access_token');
1246 if(! get_pconfig($uid,'facebook','no_wall')) {
1247 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
1248 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
1250 $j = json_decode($s);
1251 if (isset($j->data)) {
1252 logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
1253 fb_consume_stream($uid,$j,($private_wall) ? false : true);
1255 logger('fb_consume_stream: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1259 // Get the last date
1260 $lastdate = get_pconfig($uid,'facebook','lastdate');
1261 // fetch all items since the last date
1262 $j = fb_get_timeline($access_token, &$lastdate);
1263 if (isset($j->data)) {
1264 logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
1265 fb_consume_stream($uid,$j,false);
1267 // Write back the last date
1268 set_pconfig($uid,'facebook','lastdate', $lastdate);
1270 logger('fb_consume_stream: feed: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1273 function fb_get_photo($uid,$link) {
1274 $access_token = get_pconfig($uid,'facebook','access_token');
1275 if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
1277 //return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1278 $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
1280 $photo_id = $match[1];
1281 $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
1282 $j = json_decode($x);
1284 return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
1286 // return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1289 function fb_consume_stream($uid,$j,$wall = false) {
1294 $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
1300 $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1302 $no_linking = get_pconfig($uid,'facebook','no_linking');
1306 $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1310 $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
1311 $blocked_apps_arr = explode(',',$blocked_apps);
1313 $self_id = get_pconfig($uid,'facebook','self_id');
1314 if(! count($j->data) || (! strlen($self_id)))
1317 foreach($j->data as $entry) {
1318 logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
1319 $datarray = array();
1321 $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
1322 dbesc('fb::' . $entry->id),
1323 dbesc('fb::' . $entry->id),
1327 $post_exists = true;
1329 $top_item = $r[0]['id'];
1332 $post_exists = false;
1337 $datarray['gravity'] = 0;
1338 $datarray['uid'] = $uid;
1339 $datarray['wall'] = (($wall) ? 1 : 0);
1340 $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
1341 $from = $entry->from;
1342 if($from->id == $self_id)
1343 $datarray['contact-id'] = $self[0]['id'];
1345 // Looking if user is known - if not he is added
1346 $access_token = get_pconfig($uid, 'facebook', 'access_token');
1347 fb_get_friends_sync_new($uid, $access_token, $from);
1349 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1354 $datarray['contact-id'] = $r[0]['id'];
1357 // don't store post if we don't have a contact
1358 if(! x($datarray,'contact-id')) {
1359 logger('facebook: no contact '.$from->name.' '.$from->id.'. post ignored');
1363 $datarray['verb'] = ACTIVITY_POST;
1365 $datarray['owner-name'] = $self[0]['name'];
1366 $datarray['owner-link'] = $self[0]['url'];
1367 $datarray['owner-avatar'] = $self[0]['thumb'];
1369 if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
1370 $datarray['app'] = strip_tags($entry->application->name);
1372 $datarray['app'] = 'facebook';
1374 $found_blocked = false;
1376 if(count($blocked_apps_arr)) {
1377 foreach($blocked_apps_arr as $bad_appl) {
1378 if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
1379 $found_blocked = true;
1384 if($found_blocked) {
1385 logger('facebook: blocking application: ' . $datarray['app']);
1389 $datarray['author-name'] = $from->name;
1390 $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
1391 $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
1392 $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
1394 logger('facebook: post '.$entry->id.' from '.$from->name);
1396 $datarray['body'] = escape_tags($entry->message);
1398 if($entry->name and $entry->link)
1399 $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->name."[/bookmark]";
1400 elseif ($entry->name)
1401 $datarray['body'] .= "\n\n[b]" . $entry->name."[/b]";
1403 if($entry->caption) {
1404 if(!$entry->name and $entry->link)
1405 $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->caption."[/bookmark]";
1407 $datarray['body'] .= "[i]" . $entry->caption."[/i]\n";
1410 if(!$entry->caption and !$entry->name) {
1412 $datarray['body'] .= "\n[url]".$entry->link."[/url]\n";
1414 $datarray['body'] .= "\n";
1418 if($entry->description)
1419 $quote = $entry->description;
1421 if ($entry->properties)
1422 foreach ($entry->properties as $property)
1423 $quote .= "\n".$property->name.": [url=".$property->href."]".$property->text."[/url]";
1426 $datarray['body'] .= "\n[quote]".$quote."[/quote]";
1428 // Only import the picture when the message is no video
1429 // oembed display a picture of the video as well
1430 if ($entry->type != "video") {
1431 if($entry->picture && $entry->link) {
1432 $datarray['body'] .= "\n" . '[url=' . $entry->link . '][img]'.$entry->picture.'[/img][/url]';
1436 $datarray['body'] .= "\n" . '[img]' . $entry->picture . '[/img]';
1437 // if just a link, it may be a wall photo - check
1439 $datarray['body'] .= fb_get_photo($uid,$entry->link);
1443 if (($datarray['app'] == "Events") and $entry->actions)
1444 foreach ($entry->actions as $action)
1445 if ($action->name == "View")
1446 $datarray['body'] .= " [url=".$action->link."]".$entry->story."[/url]";
1448 // Just as a test - to see if these are the missing entries
1449 //if(trim($datarray['body']) == '')
1450 // $datarray['body'] = $entry->story;
1452 // Adding the "story" text to see if there are useful data in it (testing)
1453 //if (($datarray['app'] != "Events") and $entry->story)
1454 // $datarray['body'] .= "\n".$entry->story;
1456 if(trim($datarray['body']) == '') {
1457 logger('facebook: empty body '.$entry->id.' '.print_r($entry, true));
1461 $datarray['body'] .= "\n";
1464 $datarray['body'] .= "[img]".$entry->icon."[/img] ";
1466 if ($entry->actions)
1467 foreach ($entry->actions as $action)
1468 if (($action->name != "Comment") and ($action->name != "Like"))
1469 $datarray['body'] .= "[url=".$action->link."]".$action->name."[/url] ";
1471 $datarray['body'] = trim($datarray['body']);
1473 //if(($datarray['body'] != '') and ($uid == 1))
1474 // $datarray['body'] .= "[noparse]".print_r($entry, true)."[/noparse]";
1476 if ($entry->place->name or $entry->place->location->street or
1477 $entry->place->location->city or $entry->place->location->Denmark) {
1478 $datarray['coord'] = '';
1479 if ($entry->place->name)
1480 $datarray['coord'] .= $entry->place->name;
1481 if ($entry->place->location->street)
1482 $datarray['coord'] .= $entry->place->location->street;
1483 if ($entry->place->location->city)
1484 $datarray['coord'] .= " ".$entry->place->location->city;
1485 if ($entry->place->location->country)
1486 $datarray['coord'] .= " ".$entry->place->location->country;
1487 } else if ($entry->place->location->latitude and $entry->place->location->longitude)
1488 $datarray['coord'] = substr($entry->place->location->latitude, 0, 8)
1489 .' '.substr($entry->place->location->longitude, 0, 8);
1491 $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1492 $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1494 // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1495 // as the identities are from a foreign system. Mark it as private to the owner.
1497 if($entry->privacy && $entry->privacy->value !== 'EVERYONE') {
1498 $datarray['private'] = 1;
1499 $datarray['allow_cid'] = '<' . $self[0]['id'] . '>';
1502 $top_item = item_store($datarray);
1503 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1509 logger('fb: new top level item posted');
1513 if(isset($entry->likes) && isset($entry->likes->data))
1514 $likers = $entry->likes->data;
1518 if(isset($entry->comments) && isset($entry->comments->data))
1519 $comments = $entry->comments->data;
1523 if(is_array($likers)) {
1524 foreach($likers as $likes) {
1529 // If we posted the like locally, it will be found with our url, not the FB url.
1531 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id);
1533 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s'
1534 AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1535 dbesc($orig_post['uri']),
1537 dbesc(ACTIVITY_LIKE),
1538 dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1545 $likedata = array();
1546 $likedata['parent'] = $top_item;
1547 $likedata['verb'] = ACTIVITY_LIKE;
1548 $likedata['gravity'] = 3;
1549 $likedata['uid'] = $uid;
1550 $likedata['wall'] = (($wall) ? 1 : 0);
1551 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1552 $likedata['parent-uri'] = $orig_post['uri'];
1553 if($likes->id == $self_id)
1554 $likedata['contact-id'] = $self[0]['id'];
1556 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1561 $likedata['contact-id'] = $r[0]['id'];
1563 if(! x($likedata,'contact-id'))
1564 $likedata['contact-id'] = $orig_post['contact-id'];
1566 $likedata['app'] = 'facebook';
1567 $likedata['verb'] = ACTIVITY_LIKE;
1568 $likedata['author-name'] = $likes->name;
1569 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1570 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1572 $author = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1573 $objauthor = '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1574 $post_type = t('status');
1575 $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1576 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1578 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1579 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' .
1580 '<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>';
1582 $item = item_store($likedata);
1585 if(is_array($comments)) {
1586 foreach($comments as $cmnt) {
1591 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1593 dbesc('fb::' . $cmnt->id),
1594 dbesc('fb::' . $cmnt->id)
1599 $cmntdata = array();
1600 $cmntdata['parent'] = $top_item;
1601 $cmntdata['verb'] = ACTIVITY_POST;
1602 $cmntdata['gravity'] = 6;
1603 $cmntdata['uid'] = $uid;
1604 $cmntdata['wall'] = (($wall) ? 1 : 0);
1605 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1606 $cmntdata['parent-uri'] = $orig_post['uri'];
1607 if($cmnt->from->id == $self_id) {
1608 $cmntdata['contact-id'] = $self[0]['id'];
1611 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1612 dbesc($cmnt->from->id),
1616 $cmntdata['contact-id'] = $r[0]['id'];
1617 if($r[0]['blocked'] || $r[0]['readonly'])
1621 if(! x($cmntdata,'contact-id'))
1622 $cmntdata['contact-id'] = $orig_post['contact-id'];
1624 $cmntdata['app'] = 'facebook';
1625 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1626 $cmntdata['edited'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1627 $cmntdata['verb'] = ACTIVITY_POST;
1628 $cmntdata['author-name'] = $cmnt->from->name;
1629 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1630 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1631 $cmntdata['body'] = $cmnt->message;
1632 $item = item_store($cmntdata);
1634 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ",
1635 dbesc($orig_post['uri']),
1639 if(count($myconv)) {
1640 $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1642 foreach($myconv as $conv) {
1644 // now if we find a match, it means we're in this conversation
1646 if(! link_compare($conv['author-link'],$importer_url))
1649 require_once('include/enotify.php');
1651 $conv_parent = $conv['parent'];
1654 'type' => NOTIFY_COMMENT,
1655 'notify_flags' => $user[0]['notify-flags'],
1656 'language' => $user[0]['language'],
1657 'to_name' => $user[0]['username'],
1658 'to_email' => $user[0]['email'],
1659 'uid' => $user[0]['uid'],
1660 'item' => $cmntdata,
1661 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $item,
1662 'source_name' => $cmntdata['author-name'],
1663 'source_link' => $cmntdata['author-link'],
1664 'source_photo' => $cmntdata['author-avatar'],
1665 'verb' => ACTIVITY_POST,
1667 'parent' => $conv_parent,
1670 // only send one notification
1680 function fb_get_app_access_token() {
1682 $acc_token = get_config('facebook','app_access_token');
1684 if ($acc_token !== false) return $acc_token;
1686 $appid = get_config('facebook','appid');
1687 $appsecret = get_config('facebook', 'appsecret');
1689 if ($appid === false || $appsecret === false) {
1690 logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG);
1693 logger('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials', LOGGER_DATA);
1694 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials');
1696 if(strpos($x,'access_token=') !== false) {
1697 logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA);
1699 $token = str_replace('access_token=', '', $x);
1700 if(strpos($token,'&') !== false)
1701 $token = substr($token,0,strpos($token,'&'));
1704 logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG);
1707 set_config('facebook','app_access_token',$token);
1710 logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA);
1715 function facebook_subscription_del_users() {
1717 $access_token = fb_get_app_access_token();
1719 $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token;
1720 facebook_delete_url($url);
1722 if (!facebook_check_realtime_active()) del_config('facebook', 'realtime_active');
1725 function facebook_subscription_add_users($second_try = false) {
1727 $access_token = fb_get_app_access_token();
1729 $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token;
1731 list($usec, $sec) = explode(" ", microtime());
1732 $verify_token = sha1($usec . $sec . rand(0, 999999999));
1733 set_config('facebook', 'cb_verify_token', $verify_token);
1735 $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1';
1737 $j = post_url($url,array(
1739 "fields" => "feed,friends",
1740 "callback_url" => $cb,
1741 "verify_token" => $verify_token,
1743 del_config('facebook', 'cb_verify_token');
1746 $x = json_decode($j);
1747 logger("Facebook reponse: " . $j, LOGGER_DATA);
1748 if (isset($x->error)) {
1749 logger('facebook_subscription_add_users: got an error: ' . $j);
1750 if ($x->error->type == "OAuthException" && $x->error->code == 190) {
1751 del_config('facebook', 'app_access_token');
1752 if ($second_try === false) facebook_subscription_add_users(true);
1755 logger('facebook_subscription_add_users: sucessful');
1756 if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1);
1761 function facebook_subscriptions_get() {
1763 $access_token = fb_get_app_access_token();
1764 if (!$access_token) return null;
1766 $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token;
1767 $j = fetch_url($url);
1770 $x = json_decode($j);
1771 if (isset($x->data)) $ret = $x->data;
1777 function facebook_check_realtime_active() {
1778 $ret = facebook_subscriptions_get();
1779 if (is_null($ret)) return false;
1780 if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true;
1787 // DELETE-request to $url
1789 if(! function_exists('facebook_delete_url')) {
1790 function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) {
1792 $ch = curl_init($url);
1793 if(($redirects > 8) || (! $ch))
1796 curl_setopt($ch, CURLOPT_HEADER, true);
1797 curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
1798 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
1799 curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
1801 if(intval($timeout)) {
1802 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1805 $curl_time = intval(get_config('system','curl_timeout'));
1806 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
1809 if(defined('LIGHTTPD')) {
1810 if(!is_array($headers)) {
1811 $headers = array('Expect:');
1813 if(!in_array('Expect:', $headers)) {
1814 array_push($headers, 'Expect:');
1819 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1821 $check_cert = get_config('system','verifyssl');
1822 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
1823 $prx = get_config('system','proxy');
1825 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
1826 curl_setopt($ch, CURLOPT_PROXY, $prx);
1827 $prxusr = get_config('system','proxyuser');
1829 curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
1832 $a->set_curl_code(0);
1834 // don't let curl abort the entire application
1835 // if it throws any errors.
1837 $s = @curl_exec($ch);
1840 $curl_info = curl_getinfo($ch);
1841 $http_code = $curl_info['http_code'];
1845 // Pull out multiple headers, e.g. proxy and continuation headers
1846 // allow for HTTP/2.x without fixing code
1848 while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
1849 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
1851 $base = substr($base,strlen($chunk));
1854 if($http_code == 301 || $http_code == 302 || $http_code == 303) {
1856 preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
1857 $url = trim(array_pop($matches));
1858 $url_parsed = @parse_url($url);
1859 if (isset($url_parsed)) {
1861 return delete_url($url,$headers,$redirects,$timeout);
1864 $a->set_curl_code($http_code);
1865 $body = substr($s,strlen($header));
1867 $a->set_curl_headers($header);