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. (This step is now obsolete. Enable the plugin via the Admin panel.)
29 * Enable the facebook plugin by including it in .htconfig.php - e.g.
30 * $a->config['system']['addon'] = 'plugin1,plugin2,facebook';
31 * 3. Visit the Facebook Settings section of the "Settings->Plugin Settings" page.
32 * and click 'Install Facebook Connector'.
33 * 4. This will ask you to login to Facebook and grant permission to the
34 * plugin to do its stuff. Allow it to do so.
35 * 5. Optional step: If you want to use Facebook Real Time Updates (so new messages
36 * and new contacts are added ~1min after they are postet / added on FB), go to
37 * Settings -> plugins -> facebook and press the "Activate Real-Time Updates"-button.
38 * 6. You're done. To turn it off visit the Plugin Settings page again and
39 * 'Remove Facebook posting'.
41 * Vidoes and embeds will not be posted if there is no other content. Links
42 * and images will be converted to a format suitable for the Facebook API and
43 * long posts truncated - with a link to view the full post.
45 * Facebook contacts will not be able to view private photos, as they are not able to
46 * authenticate to your site to establish identity. We will address this
47 * in a future release.
50 define('FACEBOOK_MAXPOSTLEN', 420);
53 function facebook_install() {
54 register_hook('post_local', 'addon/facebook/facebook.php', 'facebook_post_local');
55 register_hook('notifier_normal', 'addon/facebook/facebook.php', 'facebook_post_hook');
56 register_hook('jot_networks', 'addon/facebook/facebook.php', 'facebook_jot_nets');
57 register_hook('connector_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings');
58 register_hook('cron', 'addon/facebook/facebook.php', 'facebook_cron');
59 register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
61 if (get_config('facebook', 'realtime_active') == 1) facebook_subscription_add_users(); // Restore settings, if the plugin was installed before
65 function facebook_uninstall() {
66 unregister_hook('post_local', 'addon/facebook/facebook.php', 'facebook_post_local');
67 unregister_hook('notifier_normal', 'addon/facebook/facebook.php', 'facebook_post_hook');
68 unregister_hook('jot_networks', 'addon/facebook/facebook.php', 'facebook_jot_nets');
69 unregister_hook('connector_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings');
70 unregister_hook('cron', 'addon/facebook/facebook.php', 'facebook_cron');
71 unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
74 unregister_hook('post_local_end', 'addon/facebook/facebook.php', 'facebook_post_hook');
75 unregister_hook('plugin_settings', 'addon/facebook/facebook.php', 'facebook_plugin_settings');
77 if (get_config('facebook', 'realtime_active') == 1) facebook_subscription_del_users();
81 /* declare the facebook_module function so that /facebook url requests will land here */
83 function facebook_module() {}
87 // If a->argv[1] is a nickname, this is a callback from Facebook oauth requests.
88 // If $_REQUEST["realtime_cb"] is set, this is a callback from the Real-Time Updates API
90 function facebook_init(&$a) {
92 if (x($_REQUEST, "realtime_cb") && x($_REQUEST, "realtime_cb")) {
93 logger("facebook_init: Facebook Real-Time callback called", LOGGER_DEBUG);
95 if (x($_REQUEST, "hub_verify_token")) {
96 // this is the verification callback while registering for real time updates
98 $verify_token = get_config('facebook', 'cb_verify_token');
99 if ($verify_token != $_REQUEST["hub_verify_token"]) {
100 logger('facebook_init: Wrong Facebook Callback Verifier - expected ' . $verify_token . ', got ' . $_REQUEST["hub_verify_token"]);
104 if (x($_REQUEST, "hub_challenge")) {
105 logger('facebook_init: Answering Challenge: ' . $_REQUEST["hub_challenge"], LOGGER_DATA);
106 echo $_REQUEST["hub_challenge"];
111 require_once('include/items.php');
113 // this is a status update
114 $content = file_get_contents("php://input");
115 if (is_numeric($content)) $content = file_get_contents("php://input");
116 $js = json_decode($content);
117 logger(print_r($js, true), LOGGER_DATA);
119 if (!isset($js->object) || $js->object != "user" || !isset($js->entry)) {
120 logger('facebook_init: Could not parse Real-Time Update data', LOGGER_DEBUG);
124 $affected_users = array("feed" => array(), "friends" => array());
126 foreach ($js->entry as $entry) {
127 $fbuser = $entry->uid;
128 foreach ($entry->changed_fields as $field) {
129 if (!isset($affected_users[$field])) {
130 logger('facebook_init: Unknown field "' . $field . '"');
133 if (in_array($fbuser, $affected_users[$field])) continue;
135 $r = q("SELECT `uid` FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'self_id' AND `v` = '%s' LIMIT 1", dbesc($fbuser));
140 $access_token = get_pconfig($uid,'facebook','access_token');
146 logger('facebook_init: FB-User ' . $fbuser . ' / feed', LOGGER_DEBUG);
148 if(! get_pconfig($uid,'facebook','no_wall')) {
149 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
150 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
152 $j = json_decode($s);
153 logger('facebook_init: wall: ' . print_r($j,true), LOGGER_DATA);
154 fb_consume_stream($uid,$j,($private_wall) ? false : true);
160 logger('facebook_init: FB-User ' . $fbuser . ' / friends', LOGGER_DEBUG);
162 fb_get_friends($uid, false);
163 set_pconfig($uid,'facebook','friend_check',time());
166 logger('facebook_init: Unknown callback field for ' . $fbuser, LOGGER_NORMAL);
168 $affected_users[$field][] = $fbuser;
178 $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1",
185 $auth_code = (x($_GET, 'code') ? $_GET['code'] : '');
186 $error = (x($_GET, 'error_description') ? $_GET['error_description'] : '');
190 logger('facebook_init: Error: ' . $error);
192 if($auth_code && $uid) {
194 $appid = get_config('facebook','appid');
195 $appsecret = get_config('facebook', 'appsecret');
197 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id='
198 . $appid . '&client_secret=' . $appsecret . '&redirect_uri='
199 . urlencode($a->get_baseurl() . '/facebook/' . $nick)
200 . '&code=' . $auth_code);
202 logger('facebook_init: returned access token: ' . $x, LOGGER_DATA);
204 if(strpos($x,'access_token=') !== false) {
205 $token = str_replace('access_token=', '', $x);
206 if(strpos($token,'&') !== false)
207 $token = substr($token,0,strpos($token,'&'));
208 set_pconfig($uid,'facebook','access_token',$token);
209 set_pconfig($uid,'facebook','post','1');
210 if(get_pconfig($uid,'facebook','no_linking') === false)
211 set_pconfig($uid,'facebook','no_linking',1);
213 fb_get_friends($uid, true);
214 fb_consume_all($uid);
223 function fb_get_self($uid) {
224 $access_token = get_pconfig($uid,'facebook','access_token');
227 $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token);
229 $j = json_decode($s);
230 set_pconfig($uid,'facebook','self_id',(string) $j->id);
234 function fb_get_friends_sync_new($uid, $access_token, $person) {
235 $link = 'http://facebook.com/profile.php?id=' . $person->id;
237 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
242 if (count($r) == 0) {
243 logger('fb_get_friends: new contact found: ' . $link, LOGGER_DEBUG);
245 fb_get_friends_sync_full($uid, $access_token, $person);
249 function fb_get_friends_sync_full($uid, $access_token, $person) {
250 $s = fetch_url('https://graph.facebook.com/' . $person->id . '?access_token=' . $access_token);
252 $jp = json_decode($s);
253 logger('fb_get_friends: info: ' . print_r($jp,true), LOGGER_DATA);
255 // always use numeric link for consistency
257 $jp->link = 'http://facebook.com/profile.php?id=' . $person->id;
259 // check if we already have a contact
261 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
268 // check that we have all the photos, this has been known to fail on occasion
270 if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro'])) {
271 require_once("Photo.php");
273 $photos = import_profile_photo('https://graph.facebook.com/' . $jp->id . '/picture', $uid, $r[0]['id']);
275 $r = q("UPDATE `contact` SET `photo` = '%s',
281 WHERE `id` = %d LIMIT 1
286 dbesc(datetime_convert()),
287 dbesc(datetime_convert()),
288 dbesc(datetime_convert()),
296 // create contact record
297 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
298 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
299 `writable`, `blocked`, `readonly`, `pending` )
300 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ",
302 dbesc(datetime_convert()),
304 dbesc(normalise_link($jp->link)),
308 dbesc('facebook ' . $jp->id),
310 dbesc(($jp->nickname) ? $jp->nickname : strtolower($jp->first_name)),
311 dbesc('https://graph.facebook.com/' . $jp->id . '/picture'),
312 dbesc(NETWORK_FACEBOOK),
313 intval(CONTACT_IS_FRIEND),
319 $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1",
329 $contact_id = $r[0]['id'];
331 require_once("Photo.php");
333 $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id);
335 $r = q("UPDATE `contact` SET `photo` = '%s',
341 WHERE `id` = %d LIMIT 1
346 dbesc(datetime_convert()),
347 dbesc(datetime_convert()),
348 dbesc(datetime_convert()),
355 // if $fullsync is true, only new contacts are searched for
357 function fb_get_friends($uid, $fullsync = true) {
359 $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
365 $access_token = get_pconfig($uid,'facebook','access_token');
367 $no_linking = get_pconfig($uid,'facebook','no_linking');
373 $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token);
375 logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA);
376 $j = json_decode($s);
377 logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA);
380 foreach($j->data as $person)
382 fb_get_friends_sync_full($uid, $access_token, $person);
384 fb_get_friends_sync_new($uid, $access_token, $person);
388 // This is the POST method to the facebook settings page
389 // Content is posted to Facebook in the function facebook_post_hook()
391 function facebook_post(&$a) {
396 $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0);
397 set_pconfig($uid,'facebook','post_by_default', $value);
399 $no_linking = get_pconfig($uid,'facebook','no_linking');
401 $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0);
402 set_pconfig($uid,'facebook','no_wall',$no_wall);
404 $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0);
405 set_pconfig($uid,'facebook','private_wall',$private_wall);
408 set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps'])));
410 $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0);
411 set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1));
413 // FB linkage was allowed but has just been turned off - remove all FB contacts and posts
415 if((! intval($no_linking)) && (! intval($linkvalue))) {
416 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ",
418 dbesc(NETWORK_FACEBOOK)
421 require_once('include/Contact.php');
423 contact_remove($rr['id']);
426 elseif(intval($no_linking) && intval($linkvalue)) {
427 // FB linkage is now allowed - import stuff.
429 fb_get_friends($uid, true);
430 fb_consume_all($uid);
433 info( t('Settings updated.') . EOL);
439 // Facebook settings form
441 function facebook_content(&$a) {
444 notice( t('Permission denied.') . EOL);
448 if($a->argc > 1 && $a->argv[1] === 'remove') {
449 del_pconfig(local_user(),'facebook','post');
450 info( t('Facebook disabled') . EOL);
453 if($a->argc > 1 && $a->argv[1] === 'friends') {
454 fb_get_friends(local_user(), true);
455 info( t('Updating contacts') . EOL);
459 $fb_installed = get_pconfig(local_user(),'facebook','post');
461 $appid = get_config('facebook','appid');
464 notice( t('Facebook API key is missing.') . EOL);
468 $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="'
469 . $a->get_baseurl() . '/addon/facebook/facebook.css' . '" media="all" />' . "\r\n";
471 $o .= '<h3>' . t('Facebook Connect') . '</h3>';
473 if(! $fb_installed) {
474 $o .= '<div id="facebook-enable-wrapper">';
476 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri='
477 . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Install Facebook connector for this account.') . '</a>';
482 $o .= '<div id="facebook-disable-wrapper">';
484 $o .= '<a href="' . $a->get_baseurl() . '/facebook/remove' . '">' . t('Remove Facebook connector') . '</a></div>';
486 $o .= '<div id="facebook-enable-wrapper">';
488 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri='
489 . $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>';
492 $o .= '<div id="facebook-post-default-form">';
493 $o .= '<form action="facebook" method="post" >';
494 $post_by_default = get_pconfig(local_user(),'facebook','post_by_default');
495 $checked = (($post_by_default) ? ' checked="checked" ' : '');
496 $o .= '<input type="checkbox" name="post_by_default" value="1"' . $checked . '/>' . ' ' . t('Post to Facebook by default') . EOL;
498 $no_linking = get_pconfig(local_user(),'facebook','no_linking');
499 $checked = (($no_linking) ? '' : ' checked="checked" ');
500 $o .= '<input type="checkbox" name="facebook_linking" value="1"' . $checked . '/>' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ;
502 $o .= '<p>' . t('Facebook conversations consist of your <em>profile wall</em> and your friend <em>stream</em>.');
503 $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.');
504 $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '</p>';
506 $private_wall = get_pconfig(local_user(),'facebook','private_wall');
507 $checked = (($private_wall) ? ' checked="checked" ' : '');
508 $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 ;
511 $no_wall = get_pconfig(local_user(),'facebook','no_wall');
512 $checked = (($no_wall) ? ' checked="checked" ' : '');
513 $o .= '<input type="checkbox" name="facebook_no_wall" value="1"' . $checked . '/>' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ;
515 $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>';
518 $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps');
520 $o .= '<div><label id="blocked-apps-label" for="blocked-apps">' . t('Comma separated applications to ignore') . ' </label></div>';
521 $o .= '<div><textarea id="blocked-apps" name="blocked_apps" >' . htmlspecialchars($blocked_apps) . '</textarea></div>';
523 $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form></div>';
531 function facebook_cron($a,$b) {
533 $last = get_config('facebook','last_poll');
535 $poll_interval = intval(get_config('facebook','poll_interval'));
537 $poll_interval = 3600;
540 $next = $last + $poll_interval;
545 logger('facebook_cron');
548 // Find the FB users on this site and randomize in case one of them
549 // uses an obscene amount of memory. It may kill this queue run
550 // but hopefully we'll get a few others through on each run.
552 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() ");
555 if(get_pconfig($rr['uid'],'facebook','no_linking'))
557 $ab = intval(get_config('system','account_abandon_days'));
559 $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1",
567 // check for new friends once a day
568 $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check');
569 if($last_friend_check)
570 $next_friend_check = $last_friend_check + 86400;
571 if($next_friend_check <= time()) {
572 fb_get_friends($rr['uid'], true);
573 set_pconfig($rr['uid'],'facebook','friend_check',time());
575 fb_consume_all($rr['uid']);
579 if (get_config('facebook', 'realtime_active') == 1) {
580 if (!facebook_check_realtime_active()) {
582 logger('facebook_cron: Facebook is not sending Real-Time Updates any more, although it is supposed to. Trying to fix it...', LOGGER_NORMAL);
583 facebook_subscription_add_users();
585 if (facebook_check_realtime_active())
586 logger('facebook_cron: Successful', LOGGER_NORMAL);
588 logger('facebook_cron: Failed', LOGGER_NORMAL);
590 if(strlen($a->config['admin_email']) && !get_config('facebook', 'realtime_err_mailsent')) {
591 $res = mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'),
592 "Hi!\n\nThere's a problem with the Facebook Real-Time Updates that cannob be solved automatically. Maybe an permission issue?\n\nThis e-mail will only be sent once.",
593 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n"
594 . 'Content-type: text/plain; charset=UTF-8' . "\n"
595 . 'Content-transfer-encoding: 8bit'
598 set_config('facebook', 'realtime_err_mailsent', 1);
601 } else { // !facebook_check_realtime_active()
602 del_config('facebook', 'realtime_err_mailsent');
606 set_config('facebook','last_poll', time());
612 function facebook_plugin_settings(&$a,&$b) {
614 $b .= '<div class="settings-block">';
615 $b .= '<h3>' . t('Facebook') . '</h3>';
616 $b .= '<a href="facebook">' . t('Facebook Connector Settings') . '</a><br />';
622 function facebook_plugin_admin(&$a, &$o){
624 $activated = facebook_check_realtime_active();
626 $o = t('Real-Time Updates are activated.') . '<br><br>';
627 $o .= '<input type="submit" name="real_time_deactivate" value="' . t('Deactivate Real-Time Updates') . '">';
629 $o = t('Real-Time Updates not activated.') . '<br><input type="submit" name="real_time_activate" value="' . t('Activate Real-Time Updates') . '">';
633 function facebook_plugin_admin_post(&$a, &$o){
634 if (x($_REQUEST,'real_time_activate')) {
635 facebook_subscription_add_users();
637 if (x($_REQUEST,'real_time_deactivate')) {
638 facebook_subscription_del_users();
642 function facebook_jot_nets(&$a,&$b) {
646 $fb_post = get_pconfig(local_user(),'facebook','post');
647 if(intval($fb_post) == 1) {
648 $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default');
649 $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : '');
650 $b .= '<div class="profile-jot-net"><input type="checkbox" name="facebook_enable"' . $selected . ' value="1" /> '
651 . t('Post to Facebook') . '</div>';
656 function facebook_post_hook(&$a,&$b) {
659 if($b['deleted'] || ($b['created'] !== $b['edited']))
663 * Post to Facebook stream
666 require_once('include/group.php');
668 logger('Facebook post');
673 $toplevel = (($b['id'] == $b['parent']) ? true : false);
676 $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1);
678 if((! $toplevel) && ($linking)) {
679 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
680 intval($b['parent']),
683 if(count($r) && substr($r[0]['uri'],0,4) === 'fb::')
684 $reply = substr($r[0]['uri'],4);
685 elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::')
686 $reply = substr($r[0]['extid'],4);
690 $u = q("SELECT * FROM user where uid = %d limit 1",
696 // only accept comments from the item owner. Other contacts are unknown to FB.
698 if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname']))
702 logger('facebook reply id=' . $reply);
705 if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) {
707 if($b['private'] && $reply === false) {
708 $allow_people = expand_acl($b['allow_cid']);
709 $allow_groups = expand_groups(expand_acl($b['allow_gid']));
710 $deny_people = expand_acl($b['deny_cid']);
711 $deny_groups = expand_groups(expand_acl($b['deny_gid']));
713 $recipients = array_unique(array_merge($allow_people,$allow_groups));
714 $deny = array_unique(array_merge($deny_people,$deny_groups));
716 $allow_str = dbesc(implode(', ',$recipients));
718 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'");
719 $allow_arr = array();
722 $allow_arr[] = $rr['notify'];
725 $deny_str = dbesc(implode(', ',$deny));
727 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'");
731 $deny_arr[] = $rr['notify'];
734 if(count($deny_arr) && (! count($allow_arr))) {
736 // One or more FB folks were denied access but nobody on FB was specifically allowed access.
737 // This might cause the post to be open to public on Facebook, but only to selected members
738 // on another network. Since this could potentially leak a post to somebody who was denied,
739 // we will skip posting it to Facebook with a slightly vague but relevant message that will
740 // hopefully lead somebody to this code comment for a better explanation of what went wrong.
742 notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL);
747 // if it's a private message but no Facebook members are allowed or denied, skip Facebook post
749 if((! count($allow_arr)) && (! count($deny_arr)))
753 if($b['verb'] == ACTIVITY_LIKE)
757 $appid = get_config('facebook', 'appid' );
758 $secret = get_config('facebook', 'appsecret' );
760 if($appid && $secret) {
762 logger('facebook: have appid+secret');
764 $fb_token = get_pconfig($b['uid'],'facebook','access_token');
767 // post to facebook if it's a public post and we've ticked the 'post to Facebook' box,
768 // or it's a private message with facebook participants
769 // or it's a reply or likes action to an existing facebook post
771 if($fb_token && ($toplevel || $b['private'] || $reply)) {
772 logger('facebook: able to post');
773 require_once('library/facebook.php');
774 require_once('include/bbcode.php');
778 logger('Facebook post: original msg=' . $msg, LOGGER_DATA);
780 // make links readable before we strip the code
782 // unless it's a dislike - just send the text as a comment
784 if($b['verb'] == ACTIVITY_DISLIKE)
785 $msg = trim(strip_tags(bbcode($msg)));
787 $search_str = $a->get_baseurl() . '/search';
789 if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
791 // don't use hashtags for message link
793 if(strpos($matches[2],$search_str) === false) {
795 if(substr($matches[2],0,5) != '[img]')
796 $linkname = $matches[2];
800 // strip tag links to avoid link clutter, this really should be
801 // configurable because we're losing information
803 $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
805 // provide the link separately for normal links
806 $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
808 if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
809 $image = $matches[1];
811 $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
813 if((strpos($link,z_root()) !== false) && (! $image))
814 $image = $a->get_baseurl() . '/images/friendica-64.jpg';
816 $msg = trim(strip_tags(bbcode($msg)));
817 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
819 // add any attachments as text urls
821 $arr = explode(',',$b['attach']);
825 foreach($arr as $r) {
827 $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
834 if (strlen($msg) > FACEBOOK_MAXPOSTLEN) {
836 require_once('library/slinky.php');
838 $display_url = $b['plink'];
840 $slinky = new Slinky( $display_url );
841 // setup a cascade of shortening services
842 // try to get a short link from these services
843 // in the order ur1.ca, trim, id.gd, tinyurl
844 $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
845 $shortlink = $slinky->short();
846 // the new message will be shortened such that "... $shortlink"
847 // will fit into the character limit
848 $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
849 $msg .= '... ' . $shortlink;
854 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
857 $postvars = array('access_token' => $fb_token);
861 'access_token' => $fb_token,
865 $postvars['picture'] = $image;
867 $postvars['link'] = $link;
869 $postvars['name'] = $linkname;
872 if(($b['private']) && ($toplevel)) {
873 $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
874 if(count($allow_arr))
875 $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
877 $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
878 $postvars['privacy'] .= '}';
883 $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
886 $url = 'https://graph.facebook.com/me/feed';
888 $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' . $b['plink'] . '"}';
891 logger('facebook: post to ' . $url);
892 logger('facebook: postvars: ' . print_r($postvars,true));
894 // "test_mode" prevents anything from actually being posted.
895 // Otherwise, let's do it.
897 if(! get_config('facebook','test_mode')) {
898 $x = post_url($url, $postvars);
900 $retj = json_decode($x);
902 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
903 dbesc('fb::' . $retj->id),
909 $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
910 require_once('include/queue_fn.php');
911 add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
912 notice( t('Facebook post failed. Queued for retry.') . EOL);
916 logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
924 function facebook_post_local(&$a,&$b) {
926 // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
927 // where we will discover it during background delivery.
929 // This can only be triggered by a local user posting to their own wall.
931 if((local_user()) && (local_user() == $b['uid'])) {
933 $fb_post = intval(get_pconfig(local_user(),'facebook','post'));
934 $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
936 // if API is used, default to the chosen settings
937 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
943 if(strlen($b['postopts']))
944 $b['postopts'] .= ',';
945 $b['postopts'] .= 'facebook';
950 function fb_queue_hook(&$a,&$b) {
952 $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
953 dbesc(NETWORK_FACEBOOK)
958 require_once('include/queue_fn.php');
961 if($x['network'] !== NETWORK_FACEBOOK)
964 logger('facebook_queue: run');
966 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid`
967 WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
975 $appid = get_config('facebook', 'appid' );
976 $secret = get_config('facebook', 'appsecret' );
978 if($appid && $secret) {
979 $fb_post = intval(get_pconfig($user['uid'],'facebook','post'));
980 $fb_token = get_pconfig($user['uid'],'facebook','access_token');
982 if($fb_post && $fb_token) {
983 logger('facebook_queue: able to post');
984 require_once('library/facebook.php');
986 $z = unserialize($x['content']);
988 $j = post_url($z['url'],$z['post']);
990 $retj = json_decode($j);
992 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
993 dbesc('fb::' . $retj->id),
996 logger('facebook_queue: success: ' . $j);
997 remove_queue_item($x['id']);
1000 logger('facebook_queue: failed: ' . $j);
1001 update_queue_time($x['id']);
1008 function fb_consume_all($uid) {
1010 require_once('include/items.php');
1012 $access_token = get_pconfig($uid,'facebook','access_token');
1016 if(! get_pconfig($uid,'facebook','no_wall')) {
1017 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
1018 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
1020 $j = json_decode($s);
1021 logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
1022 fb_consume_stream($uid,$j,($private_wall) ? false : true);
1025 $s = fetch_url('https://graph.facebook.com/me/home?access_token=' . $access_token);
1027 $j = json_decode($s);
1028 logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
1029 fb_consume_stream($uid,$j,false);
1034 function fb_get_photo($uid,$link) {
1035 $access_token = get_pconfig($uid,'facebook','access_token');
1036 if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
1037 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1038 $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
1040 $photo_id = $match[1];
1041 $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
1042 $j = json_decode($x);
1044 return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
1046 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1049 function fb_consume_stream($uid,$j,$wall = false) {
1054 $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
1060 $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1062 $no_linking = get_pconfig($uid,'facebook','no_linking');
1066 $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1070 $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
1071 $blocked_apps_arr = explode(',',$blocked_apps);
1073 $self_id = get_pconfig($uid,'facebook','self_id');
1074 if(! count($j->data) || (! strlen($self_id)))
1077 foreach($j->data as $entry) {
1078 logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
1079 $datarray = array();
1081 $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
1082 dbesc('fb::' . $entry->id),
1083 dbesc('fb::' . $entry->id),
1087 $post_exists = true;
1089 $top_item = $r[0]['id'];
1092 $post_exists = false;
1097 $datarray['gravity'] = 0;
1098 $datarray['uid'] = $uid;
1099 $datarray['wall'] = (($wall) ? 1 : 0);
1100 $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
1101 $from = $entry->from;
1102 if($from->id == $self_id)
1103 $datarray['contact-id'] = $self[0]['id'];
1105 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1110 $datarray['contact-id'] = $r[0]['id'];
1113 // don't store post if we don't have a contact
1115 if(! x($datarray,'contact-id')) {
1116 logger('no contact: post ignored');
1120 $datarray['verb'] = ACTIVITY_POST;
1122 $datarray['owner-name'] = $self[0]['name'];
1123 $datarray['owner-link'] = $self[0]['url'];
1124 $datarray['owner-avatar'] = $self[0]['thumb'];
1126 if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
1127 $datarray['app'] = strip_tags($entry->application->name);
1129 $datarray['app'] = 'facebook';
1131 $found_blocked = false;
1133 if(count($blocked_apps_arr)) {
1134 foreach($blocked_apps_arr as $bad_appl) {
1135 if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
1136 $found_blocked = true;
1141 if($found_blocked) {
1142 logger('facebook: blocking application: ' . $datarray['app']);
1146 $datarray['author-name'] = $from->name;
1147 $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
1148 $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
1149 $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
1151 $datarray['body'] = escape_tags($entry->message);
1153 if($entry->picture && $entry->link) {
1154 $datarray['body'] .= "\n\n" . '[url=' . $entry->link . '][img]' . $entry->picture . '[/img][/url]';
1158 $datarray['body'] .= "\n\n" . '[img]' . $entry->picture . '[/img]';
1159 // if just a link, it may be a wall photo - check
1161 $datarray['body'] .= fb_get_photo($uid,$entry->link);
1164 $datarray['body'] .= "\n" . $entry->name;
1166 $datarray['body'] .= "\n" . $entry->caption;
1167 if($entry->description)
1168 $datarray['body'] .= "\n" . $entry->description;
1169 $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1170 $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1172 // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1173 // as the identities are from a foreign system. Mark it as private to the owner.
1175 if($entry->privacy && $entry->privacy->value !== 'EVERYONE') {
1176 $datarray['private'] = 1;
1177 $datarray['allow_cid'] = '<' . $self[0]['id'] . '>';
1180 if(trim($datarray['body']) == '') {
1181 logger('facebook: empty body');
1185 $top_item = item_store($datarray);
1186 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1192 logger('fb: new top level item posted');
1196 if(isset($entry->likes) && isset($entry->likes->data))
1197 $likers = $entry->likes->data;
1201 if(isset($entry->comments) && isset($entry->comments->data))
1202 $comments = $entry->comments->data;
1206 if(is_array($likers)) {
1207 foreach($likers as $likes) {
1212 // If we posted the like locally, it will be found with our url, not the FB url.
1214 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id);
1216 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s'
1217 AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1218 dbesc($orig_post['uri']),
1220 dbesc(ACTIVITY_LIKE),
1221 dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1228 $likedata = array();
1229 $likedata['parent'] = $top_item;
1230 $likedata['verb'] = ACTIVITY_LIKE;
1231 $likedata['gravity'] = 3;
1232 $likedata['uid'] = $uid;
1233 $likedata['wall'] = (($wall) ? 1 : 0);
1234 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1235 $likedata['parent-uri'] = $orig_post['uri'];
1236 if($likes->id == $self_id)
1237 $likedata['contact-id'] = $self[0]['id'];
1239 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1244 $likedata['contact-id'] = $r[0]['id'];
1246 if(! x($likedata,'contact-id'))
1247 $likedata['contact-id'] = $orig_post['contact-id'];
1249 $likedata['app'] = 'facebook';
1250 $likedata['verb'] = ACTIVITY_LIKE;
1251 $likedata['author-name'] = $likes->name;
1252 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1253 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1255 $author = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1256 $objauthor = '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1257 $post_type = t('status');
1258 $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1259 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1261 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1262 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' .
1263 '<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>';
1265 $item = item_store($likedata);
1268 if(is_array($comments)) {
1269 foreach($comments as $cmnt) {
1274 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1276 dbesc('fb::' . $cmnt->id),
1277 dbesc('fb::' . $cmnt->id)
1282 $cmntdata = array();
1283 $cmntdata['parent'] = $top_item;
1284 $cmntdata['verb'] = ACTIVITY_POST;
1285 $cmntdata['gravity'] = 6;
1286 $cmntdata['uid'] = $uid;
1287 $cmntdata['wall'] = (($wall) ? 1 : 0);
1288 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1289 $cmntdata['parent-uri'] = $orig_post['uri'];
1290 if($cmnt->from->id == $self_id) {
1291 $cmntdata['contact-id'] = $self[0]['id'];
1294 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1295 dbesc($cmnt->from->id),
1299 $cmntdata['contact-id'] = $r[0]['id'];
1300 if($r[0]['blocked'] || $r[0]['readonly'])
1304 if(! x($cmntdata,'contact-id'))
1305 $cmntdata['contact-id'] = $orig_post['contact-id'];
1307 $cmntdata['app'] = 'facebook';
1308 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1309 $cmntdata['edited'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1310 $cmntdata['verb'] = ACTIVITY_POST;
1311 $cmntdata['author-name'] = $cmnt->from->name;
1312 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1313 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1314 $cmntdata['body'] = $cmnt->message;
1315 $item = item_store($cmntdata);
1317 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ",
1318 dbesc($orig_post['uri']),
1322 if(count($myconv)) {
1323 $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1325 foreach($myconv as $conv) {
1327 // now if we find a match, it means we're in this conversation
1329 if(! link_compare($conv['author-link'],$importer_url))
1332 require_once('include/enotify.php');
1334 $conv_parent = $conv['parent'];
1337 'type' => NOTIFY_COMMENT,
1338 'notify_flags' => $user[0]['notify-flags'],
1339 'language' => $user[0]['language'],
1340 'to_name' => $user[0]['username'],
1341 'to_email' => $user[0]['email'],
1342 'uid' => $user[0]['uid'],
1343 'item' => $cmntdata,
1344 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $item,
1345 'source_name' => $cmntdata['author-name'],
1346 'source_link' => $cmntdata['author-link'],
1347 'source_photo' => $cmntdata['author-avatar'],
1348 'verb' => ACTIVITY_POST,
1350 'parent' => $conv_parent,
1353 // only send one notification
1363 function fb_get_app_access_token() {
1365 $acc_token = get_config('facebook','app_access_token');
1367 if ($acc_token !== false) return $acc_token;
1369 $appid = get_config('facebook','appid');
1370 $appsecret = get_config('facebook', 'appsecret');
1372 if ($appid === false || $appsecret === false) {
1373 logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG);
1377 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . "&grant_type=client_credentials");
1379 if(strpos($x,'access_token=') !== false) {
1380 logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA);
1382 $token = str_replace('access_token=', '', $x);
1383 if(strpos($token,'&') !== false)
1384 $token = substr($token,0,strpos($token,'&'));
1387 logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG);
1390 set_config('facebook','app_access_token',$token);
1393 logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA);
1398 function facebook_subscription_del_users() {
1400 $access_token = fb_get_app_access_token();
1402 $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token;
1403 facebook_delete_url($url);
1405 del_config('facebook', 'realtime_active');
1408 function facebook_subscription_add_users() {
1411 $access_token = fb_get_app_access_token();
1413 $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token;
1415 list($usec, $sec) = explode(" ", microtime());
1416 $verify_token = sha1($usec . $sec . rand(0, 999999999));
1417 set_config('facebook', 'cb_verify_token', $verify_token);
1419 $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1';
1421 $j = post_url($url,array(
1423 "fields" => "feed,friends",
1424 "callback_url" => $cb,
1425 "verify_token" => $verify_token,
1427 del_config('facebook', 'cb_verify_token');
1430 logger("Facebook reponse: " . $j, LOGGER_DATA);
1432 if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1);
1436 function facebook_subscriptions_get() {
1438 $access_token = fb_get_app_access_token();
1439 if (!$access_token) return null;
1441 $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token;
1442 $j = fetch_url($url);
1445 $x = json_decode($j);
1446 if (isset($x->data)) $ret = $x->data;
1452 function facebook_check_realtime_active() {
1453 $ret = facebook_subscriptions_get();
1454 if (is_null($ret)) return false;
1455 if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true;
1462 // DELETE-request to $url
1464 if(! function_exists('facebook_delete_url')) {
1465 function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) {
1467 $ch = curl_init($url);
1468 if(($redirects > 8) || (! $ch))
1471 curl_setopt($ch, CURLOPT_HEADER, true);
1472 curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
1473 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
1474 curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
1476 if(intval($timeout)) {
1477 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1480 $curl_time = intval(get_config('system','curl_timeout'));
1481 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
1484 if(defined('LIGHTTPD')) {
1485 if(!is_array($headers)) {
1486 $headers = array('Expect:');
1488 if(!in_array('Expect:', $headers)) {
1489 array_push($headers, 'Expect:');
1494 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1496 $check_cert = get_config('system','verifyssl');
1497 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
1498 $prx = get_config('system','proxy');
1500 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
1501 curl_setopt($ch, CURLOPT_PROXY, $prx);
1502 $prxusr = get_config('system','proxyuser');
1504 curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
1507 $a->set_curl_code(0);
1509 // don't let curl abort the entire application
1510 // if it throws any errors.
1512 $s = @curl_exec($ch);
1515 $curl_info = curl_getinfo($ch);
1516 $http_code = $curl_info['http_code'];
1520 // Pull out multiple headers, e.g. proxy and continuation headers
1521 // allow for HTTP/2.x without fixing code
1523 while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
1524 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
1526 $base = substr($base,strlen($chunk));
1529 if($http_code == 301 || $http_code == 302 || $http_code == 303) {
1531 preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
1532 $url = trim(array_pop($matches));
1533 $url_parsed = @parse_url($url);
1534 if (isset($url_parsed)) {
1536 return delete_url($url,$headers,$redirects,$timeout);
1539 $a->set_curl_code($http_code);
1540 $body = substr($s,strlen($header));
1542 $a->set_curl_headers($header);