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 `nickname`, `blockwall` 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'] = '<' . $uid . '>';
1180 $top_item = item_store($datarray);
1181 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1187 logger('fb: new top level item posted');
1191 if(isset($entry->likes) && isset($entry->likes->data))
1192 $likers = $entry->likes->data;
1196 if(isset($entry->comments) && isset($entry->comments->data))
1197 $comments = $entry->comments->data;
1201 if(is_array($likers)) {
1202 foreach($likers as $likes) {
1207 // If we posted the like locally, it will be found with our url, not the FB url.
1209 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id);
1211 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s'
1212 AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1213 dbesc($orig_post['uri']),
1215 dbesc(ACTIVITY_LIKE),
1216 dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1223 $likedata = array();
1224 $likedata['parent'] = $top_item;
1225 $likedata['verb'] = ACTIVITY_LIKE;
1226 $likedata['gravity'] = 3;
1227 $likedata['uid'] = $uid;
1228 $likedata['wall'] = (($wall) ? 1 : 0);
1229 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1230 $likedata['parent-uri'] = $orig_post['uri'];
1231 if($likes->id == $self_id)
1232 $likedata['contact-id'] = $self[0]['id'];
1234 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1239 $likedata['contact-id'] = $r[0]['id'];
1241 if(! x($likedata,'contact-id'))
1242 $likedata['contact-id'] = $orig_post['contact-id'];
1244 $likedata['app'] = 'facebook';
1245 $likedata['verb'] = ACTIVITY_LIKE;
1246 $likedata['author-name'] = $likes->name;
1247 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1248 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1250 $author = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1251 $objauthor = '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1252 $post_type = t('status');
1253 $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1254 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1256 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1257 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' .
1258 '<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>';
1260 $item = item_store($likedata);
1263 if(is_array($comments)) {
1264 foreach($comments as $cmnt) {
1269 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1271 dbesc('fb::' . $cmnt->id),
1272 dbesc('fb::' . $cmnt->id)
1277 $cmntdata = array();
1278 $cmntdata['parent'] = $top_item;
1279 $cmntdata['verb'] = ACTIVITY_POST;
1280 $cmntdata['gravity'] = 6;
1281 $cmntdata['uid'] = $uid;
1282 $cmntdata['wall'] = (($wall) ? 1 : 0);
1283 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1284 $cmntdata['parent-uri'] = $orig_post['uri'];
1285 if($cmnt->from->id == $self_id) {
1286 $cmntdata['contact-id'] = $self[0]['id'];
1289 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1290 dbesc($cmnt->from->id),
1294 $cmntdata['contact-id'] = $r[0]['id'];
1295 if($r[0]['blocked'] || $r[0]['readonly'])
1299 if(! x($cmntdata,'contact-id'))
1300 $cmntdata['contact-id'] = $orig_post['contact-id'];
1302 $cmntdata['app'] = 'facebook';
1303 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1304 $cmntdata['edited'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1305 $cmntdata['verb'] = ACTIVITY_POST;
1306 $cmntdata['author-name'] = $cmnt->from->name;
1307 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1308 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1309 $cmntdata['body'] = $cmnt->message;
1310 $item = item_store($cmntdata);
1317 function fb_get_app_access_token() {
1319 $acc_token = get_config('facebook','app_access_token');
1321 if ($acc_token !== false) return $acc_token;
1323 $appid = get_config('facebook','appid');
1324 $appsecret = get_config('facebook', 'appsecret');
1326 if ($appid === false || $appsecret === false) {
1327 logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG);
1331 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . "&grant_type=client_credentials");
1333 if(strpos($x,'access_token=') !== false) {
1334 logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA);
1336 $token = str_replace('access_token=', '', $x);
1337 if(strpos($token,'&') !== false)
1338 $token = substr($token,0,strpos($token,'&'));
1341 logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG);
1344 set_config('facebook','app_access_token',$token);
1347 logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA);
1352 function facebook_subscription_del_users() {
1354 $access_token = fb_get_app_access_token();
1356 $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token;
1357 facebook_delete_url($url);
1359 del_config('facebook', 'realtime_active');
1362 function facebook_subscription_add_users() {
1365 $access_token = fb_get_app_access_token();
1367 $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token;
1369 list($usec, $sec) = explode(" ", microtime());
1370 $verify_token = sha1($usec . $sec . rand(0, 999999999));
1371 set_config('facebook', 'cb_verify_token', $verify_token);
1373 $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1';
1375 $j = post_url($url,array(
1377 "fields" => "feed,friends",
1378 "callback_url" => $cb,
1379 "verify_token" => $verify_token,
1381 del_config('facebook', 'cb_verify_token');
1384 logger("Facebook reponse: " . $j, LOGGER_DATA);
1386 if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1);
1390 function facebook_subscriptions_get() {
1392 $access_token = fb_get_app_access_token();
1393 if (!$access_token) return null;
1395 $url = "https://graph.facebook.com/" . get_config('facebook', 'appid' ) . "/subscriptions?access_token=" . $access_token;
1396 $j = fetch_url($url);
1399 $x = json_decode($j);
1400 if (isset($x->data)) $ret = $x->data;
1406 function facebook_check_realtime_active() {
1407 $ret = facebook_subscriptions_get();
1408 if (is_null($ret)) return false;
1409 if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true;
1416 // DELETE-request to $url
1418 if(! function_exists('facebook_delete_url')) {
1419 function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) {
1421 $ch = curl_init($url);
1422 if(($redirects > 8) || (! $ch))
1425 curl_setopt($ch, CURLOPT_HEADER, true);
1426 curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
1427 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
1428 curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
1430 if(intval($timeout)) {
1431 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1434 $curl_time = intval(get_config('system','curl_timeout'));
1435 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
1438 if(defined('LIGHTTPD')) {
1439 if(!is_array($headers)) {
1440 $headers = array('Expect:');
1442 if(!in_array('Expect:', $headers)) {
1443 array_push($headers, 'Expect:');
1448 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1450 $check_cert = get_config('system','verifyssl');
1451 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
1452 $prx = get_config('system','proxy');
1454 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
1455 curl_setopt($ch, CURLOPT_PROXY, $prx);
1456 $prxusr = get_config('system','proxyuser');
1458 curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
1461 $a->set_curl_code(0);
1463 // don't let curl abort the entire application
1464 // if it throws any errors.
1466 $s = @curl_exec($ch);
1469 $curl_info = curl_getinfo($ch);
1470 $http_code = $curl_info['http_code'];
1474 // Pull out multiple headers, e.g. proxy and continuation headers
1475 // allow for HTTP/2.x without fixing code
1477 while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
1478 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
1480 $base = substr($base,strlen($chunk));
1483 if($http_code == 301 || $http_code == 302 || $http_code == 303) {
1485 preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
1486 $url = trim(array_pop($matches));
1487 $url_parsed = @parse_url($url);
1488 if (isset($url_parsed)) {
1490 return delete_url($url,$headers,$redirects,$timeout);
1493 $a->set_curl_code($http_code);
1494 $body = substr($s,strlen($header));
1496 $a->set_curl_headers($header);