]> git.mxchange.org Git - friendica-addons.git/blob - facebook/facebook.php
Use batch requests when syncing friends; this reduces the time for synchronization...
[friendica-addons.git] / facebook / facebook.php
1 <?php
2 /**
3  * Name: Facebook Connector
4  * Version: 1.1
5  * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
6  *         Tobias Hößl <https://github.com/CatoTH/>
7  */
8
9 /**
10  * Installing the Friendica/Facebook connector
11  *
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
27  *      yourdomain.com.
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'.
39  *
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.
43  *
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.
47  */
48
49 /** TODO
50  * - Implement a method for the administrator to delete all configuration data the plugin has created,
51  *   e.g. the app_access_token
52  */
53
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);
61
62
63 function facebook_install() {
64     register_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
65     register_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
66     register_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
67     register_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
68     register_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
69     register_hook('enotify',          'addon/facebook/facebook.php', 'facebook_enotify');
70     register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
71 }
72
73
74 function facebook_uninstall() {
75     unregister_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
76     unregister_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
77     unregister_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
78     unregister_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
79     unregister_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
80     unregister_hook('enotify',          'addon/facebook/facebook.php', 'facebook_enotify');
81     unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
82
83     // hook moved
84     unregister_hook('post_local_end',  'addon/facebook/facebook.php', 'facebook_post_hook');
85     unregister_hook('plugin_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
86 }
87
88
89 /* declare the facebook_module function so that /facebook url requests will land here */
90
91 function facebook_module() {}
92
93
94
95 // If a->argv[1] is a nickname, this is a callback from Facebook oauth requests.
96 // If $_REQUEST["realtime_cb"] is set, this is a callback from the Real-Time Updates API
97
98 function facebook_init(&$a) {
99
100     if (x($_REQUEST, "realtime_cb") && x($_REQUEST, "realtime_cb")) {
101         logger("facebook_init: Facebook Real-Time callback called", LOGGER_DEBUG);
102
103         if (x($_REQUEST, "hub_verify_token")) {
104             // this is the verification callback while registering for real time updates
105
106             $verify_token = get_config('facebook', 'cb_verify_token');
107             if ($verify_token != $_REQUEST["hub_verify_token"]) {
108                 logger('facebook_init: Wrong Facebook Callback Verifier - expected ' . $verify_token . ', got ' . $_REQUEST["hub_verify_token"]);
109                 return;
110             }
111
112             if (x($_REQUEST, "hub_challenge")) {
113                 logger('facebook_init: Answering Challenge: ' . $_REQUEST["hub_challenge"], LOGGER_DATA);
114                 echo $_REQUEST["hub_challenge"];
115                 die();
116             }
117         }
118
119         require_once('include/items.php');
120
121         // this is a status update
122         $content = file_get_contents("php://input");
123         if (is_numeric($content)) $content = file_get_contents("php://input");
124         $js = json_decode($content);
125         logger(print_r($js, true), LOGGER_DATA);
126
127         if (!isset($js->object) || $js->object != "user" || !isset($js->entry)) {
128             logger('facebook_init: Could not parse Real-Time Update data', LOGGER_DEBUG);
129             return;
130         }
131
132         $affected_users = array("feed" => array(), "friends" => array());
133
134         foreach ($js->entry as $entry) {
135             $fbuser = $entry->uid;
136             foreach ($entry->changed_fields as $field) {
137                 if (!isset($affected_users[$field])) {
138                     logger('facebook_init: Unknown field "' . $field . '"');
139                     continue;
140                 }
141                 if (in_array($fbuser, $affected_users[$field])) continue;
142
143                 $r = q("SELECT `uid` FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'self_id' AND `v` = '%s' LIMIT 1", dbesc($fbuser));
144                 if(! count($r))
145                     continue;
146                 $uid = $r[0]['uid'];
147
148                 $access_token = get_pconfig($uid,'facebook','access_token');
149                 if(! $access_token)
150                     return;
151
152                 switch ($field) {
153                     case "feed":
154                         logger('facebook_init: FB-User ' . $fbuser . ' / feed', LOGGER_DEBUG);
155
156                         if(! get_pconfig($uid,'facebook','no_wall')) {
157                             $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
158                             $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
159                             if($s) {
160                                 $j = json_decode($s);
161                                 if (isset($j->data)) {
162                                     logger('facebook_init: wall: ' . print_r($j,true), LOGGER_DATA);
163                                     fb_consume_stream($uid,$j,($private_wall) ? false : true);
164                                 } else {
165                                     logger('facebook_init: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
166                                 }
167                             }
168                         }
169
170                         break;
171                     case "friends":
172                         logger('facebook_init: FB-User ' . $fbuser . ' / friends', LOGGER_DEBUG);
173
174                         fb_get_friends($uid, false);
175                         set_pconfig($uid,'facebook','friend_check',time());
176                         break;
177                     default:
178                         logger('facebook_init: Unknown callback field for ' . $fbuser, LOGGER_NORMAL);
179                 }
180                 $affected_users[$field][] = $fbuser;
181             }
182         }
183     }
184
185
186     if($a->argc != 2)
187         return;
188     $nick = $a->argv[1];
189     if(strlen($nick))
190         $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1",
191             dbesc($nick)
192         );
193     if(! count($r))
194         return;
195
196     $uid           = $r[0]['uid'];
197     $auth_code     = (x($_GET, 'code') ? $_GET['code'] : '');
198     $error         = (x($_GET, 'error_description') ? $_GET['error_description'] : '');
199
200     if($error)
201         logger('facebook_init: Error: ' . $error);
202
203     if($auth_code && $uid) {
204
205         $appid = get_config('facebook','appid');
206         $appsecret = get_config('facebook', 'appsecret');
207
208         $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id='
209             . $appid . '&client_secret=' . $appsecret . '&redirect_uri='
210             . urlencode($a->get_baseurl() . '/facebook/' . $nick)
211             . '&code=' . $auth_code);
212
213         logger('facebook_init: returned access token: ' . $x, LOGGER_DATA);
214
215         if(strpos($x,'access_token=') !== false) {
216             $token = str_replace('access_token=', '', $x);
217             if(strpos($token,'&') !== false)
218                 $token = substr($token,0,strpos($token,'&'));
219             set_pconfig($uid,'facebook','access_token',$token);
220             set_pconfig($uid,'facebook','post','1');
221             if(get_pconfig($uid,'facebook','no_linking') === false)
222                 set_pconfig($uid,'facebook','no_linking',1);
223             fb_get_self($uid);
224             fb_get_friends($uid, true);
225             fb_consume_all($uid);
226
227         }
228
229     }
230
231 }
232
233
234 function fb_get_self($uid) {
235     $access_token = get_pconfig($uid,'facebook','access_token');
236     if(! $access_token)
237         return;
238     $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token);
239     if($s) {
240         $j = json_decode($s);
241         set_pconfig($uid,'facebook','self_id',(string) $j->id);
242     }
243 }
244
245 function fb_get_friends_sync_new($uid, $access_token, $persons) {
246     $persons_todo = array();
247     foreach ($persons as $person) {
248         $link = 'http://facebook.com/profile.php?id=' . $person->id;
249
250         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
251             intval($uid),
252             dbesc($link)
253         );
254
255         if (count($r) == 0) {
256             logger('fb_get_friends: new contact found: ' . $link, LOGGER_DEBUG);
257             $persons_todo[] = $person;
258         }
259
260         if (count($persons_todo) > 0) fb_get_friends_sync_full($uid, $access_token, $persons_todo);
261     }
262 }
263
264 function fb_get_friends_sync_parsecontact($uid, $contact) {
265     $contact->link = 'http://facebook.com/profile.php?id=' . $contact->id;
266
267     // If its a page then set the first name from the username
268     if (!$contact->first_name and $contact->username)
269         $contact->first_name = $contact->username;
270
271     // check if we already have a contact
272
273     $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
274         intval($uid),
275         dbesc($contact->link)
276     );
277
278     if(count($r)) {
279
280         // check that we have all the photos, this has been known to fail on occasion
281
282         if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro'])) {
283             require_once("Photo.php");
284
285             $photos = import_profile_photo('https://graph.facebook.com/' . $contact->id . '/picture', $uid, $r[0]['id']);
286
287             $r = q("UPDATE `contact` SET `photo` = '%s', 
288                                         `thumb` = '%s',
289                                         `micro` = '%s', 
290                                         `name-date` = '%s', 
291                                         `uri-date` = '%s', 
292                                         `avatar-date` = '%s'
293                                         WHERE `id` = %d LIMIT 1
294                                 ",
295                 dbesc($photos[0]),
296                 dbesc($photos[1]),
297                 dbesc($photos[2]),
298                 dbesc(datetime_convert()),
299                 dbesc(datetime_convert()),
300                 dbesc(datetime_convert()),
301                 intval($r[0]['id'])
302             );
303         }
304         return;
305     }
306     else {
307
308         // create contact record 
309         $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, 
310                                 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
311                                 `writable`, `blocked`, `readonly`, `pending` )
312                                 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ",
313             intval($uid),
314             dbesc(datetime_convert()),
315             dbesc($contact->link),
316             dbesc(normalise_link($contact->link)),
317             dbesc(''),
318             dbesc(''),
319             dbesc($contact->id),
320             dbesc('facebook ' . $contact->id),
321             dbesc($contact->name),
322             dbesc(($contact->nickname) ? $contact->nickname : strtolower($contact->first_name)),
323             dbesc('https://graph.facebook.com/' . $contact->id . '/picture'),
324             dbesc(NETWORK_FACEBOOK),
325             intval(CONTACT_IS_FRIEND),
326             intval(1),
327             intval(1)
328         );
329     }
330
331     $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1",
332         dbesc($contact->link),
333         intval($uid)
334     );
335
336     if(! count($r)) {
337         return;
338     }
339
340     $contact = $r[0];
341     $contact_id  = $r[0]['id'];
342
343     require_once("Photo.php");
344
345     $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id);
346
347     $r = q("UPDATE `contact` SET `photo` = '%s', 
348                         `thumb` = '%s',
349                         `micro` = '%s', 
350                         `name-date` = '%s', 
351                         `uri-date` = '%s', 
352                         `avatar-date` = '%s'
353                         WHERE `id` = %d LIMIT 1
354                 ",
355         dbesc($photos[0]),
356         dbesc($photos[1]),
357         dbesc($photos[2]),
358         dbesc(datetime_convert()),
359         dbesc(datetime_convert()),
360         dbesc(datetime_convert()),
361         intval($contact_id)
362     );
363 }
364
365 function fb_get_friends_sync_full($uid, $access_token, $persons) {
366     if (count($persons) == 0) return;
367     $nums = Ceil(count($persons) / 50);
368     for ($i = 0; $i < $nums; $i++) {
369         $batch_request = array();
370         for ($j = $i * 50; $j < ($i+1) * 50 && $j < count($persons); $j++) $batch_request[] = array('method'=>'GET', 'relative_url'=>$persons[$j]->id);
371         $s = post_url('https://graph.facebook.com/', array('access_token' => $access_token, 'batch' => json_encode($batch_request)));
372         if($s) {
373             $results = json_decode($s);
374             logger('fb_get_friends: info: ' . print_r($results,true), LOGGER_DATA);
375             foreach ($results as $contact) {
376                 if ($contact->code != 200) logger('fb_get_friends: not found: ' . print_r($contact,true), LOGGER_DEBUG);
377                 else fb_get_friends_sync_parsecontact($uid, json_decode($contact->body));
378             }
379         }
380     }
381 }
382
383 // if $fullsync is true, only new contacts are searched for
384
385 function fb_get_friends($uid, $fullsync = true) {
386
387     $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
388         intval($uid)
389     );
390     if(! count($r))
391         return;
392
393     $access_token = get_pconfig($uid,'facebook','access_token');
394
395     $no_linking = get_pconfig($uid,'facebook','no_linking');
396     if($no_linking)
397         return;
398
399     if(! $access_token)
400         return;
401     $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token);
402     if($s) {
403         logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA);
404         $j = json_decode($s);
405         logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA);
406         if(! $j->data)
407             return;
408
409         $persons_todo = array();
410         foreach($j->data as $person) $persons_todo[] = $person;
411
412         if ($fullsync)
413             fb_get_friends_sync_full($uid, $access_token, $persons_todo);
414         else
415             fb_get_friends_sync_new($uid, $access_token, $persons_todo);
416     }
417 }
418
419 // This is the POST method to the facebook settings page
420 // Content is posted to Facebook in the function facebook_post_hook() 
421
422 function facebook_post(&$a) {
423
424     $uid = local_user();
425     if($uid){
426
427         $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0);
428         set_pconfig($uid,'facebook','post_by_default', $value);
429
430         $no_linking = get_pconfig($uid,'facebook','no_linking');
431
432         $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0);
433         set_pconfig($uid,'facebook','no_wall',$no_wall);
434
435         $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0);
436         set_pconfig($uid,'facebook','private_wall',$private_wall);
437
438
439         set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps'])));
440
441         $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0);
442         set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1));
443
444         // FB linkage was allowed but has just been turned off - remove all FB contacts and posts
445
446         if((! intval($no_linking)) && (! intval($linkvalue))) {
447             $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ",
448                 intval($uid),
449                 dbesc(NETWORK_FACEBOOK)
450             );
451             if(count($r)) {
452                 require_once('include/Contact.php');
453                 foreach($r as $rr)
454                     contact_remove($rr['id']);
455             }
456         }
457         elseif(intval($no_linking) && intval($linkvalue)) {
458             // FB linkage is now allowed - import stuff.
459             fb_get_self($uid);
460             fb_get_friends($uid, true);
461             fb_consume_all($uid);
462         }
463
464         info( t('Settings updated.') . EOL);
465     }
466
467     return;
468 }
469
470 // Facebook settings form
471
472 function facebook_content(&$a) {
473
474     if(! local_user()) {
475         notice( t('Permission denied.') . EOL);
476         return '';
477     }
478
479     if($a->argc > 1 && $a->argv[1] === 'remove') {
480         del_pconfig(local_user(),'facebook','post');
481         info( t('Facebook disabled') . EOL);
482     }
483
484     if($a->argc > 1 && $a->argv[1] === 'friends') {
485         fb_get_friends(local_user(), true);
486         info( t('Updating contacts') . EOL);
487     }
488
489     $o = '';
490
491     $fb_installed = false;
492     if (get_pconfig(local_user(),'facebook','post')) {
493         $access_token = get_pconfig(local_user(),'facebook','access_token');
494         if ($access_token) {
495             $private_wall = intval(get_pconfig(local_user(),'facebook','private_wall'));
496             $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
497             if($s) {
498                 $j = json_decode($s);
499                 if (isset($j->data)) $fb_installed = true;
500             }
501         }
502     }
503
504     $appid = get_config('facebook','appid');
505
506     if(! $appid) {
507         notice( t('Facebook API key is missing.') . EOL);
508         return '';
509     }
510
511     $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="'
512         . $a->get_baseurl() . '/addon/facebook/facebook.css' . '" media="all" />' . "\r\n";
513
514     $o .= '<h3>' . t('Facebook Connect') . '</h3>';
515
516     if(! $fb_installed) {
517         $o .= '<div id="facebook-enable-wrapper">';
518
519         $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri='
520             . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Install Facebook connector for this account.') . '</a>';
521         $o .= '</div>';
522     }
523
524     if($fb_installed) {
525         $o .= '<div id="facebook-disable-wrapper">';
526
527         $o .= '<a href="' . $a->get_baseurl() . '/facebook/remove' . '">' . t('Remove Facebook connector') . '</a></div>';
528
529         $o .= '<div id="facebook-enable-wrapper">';
530
531         $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri='
532             . $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>';
533         $o .= '</div>';
534
535         $o .= '<div id="facebook-post-default-form">';
536         $o .= '<form action="facebook" method="post" >';
537         $post_by_default = get_pconfig(local_user(),'facebook','post_by_default');
538         $checked = (($post_by_default) ? ' checked="checked" ' : '');
539         $o .= '<input type="checkbox" name="post_by_default" value="1"' . $checked . '/>' . ' ' . t('Post to Facebook by default') . EOL;
540
541         $no_linking = get_pconfig(local_user(),'facebook','no_linking');
542         $checked = (($no_linking) ? '' : ' checked="checked" ');
543         $o .= '<input type="checkbox" name="facebook_linking" value="1"' . $checked . '/>' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ;
544
545         $o .= '<p>' . t('Facebook conversations consist of your <em>profile wall</em> and your friend <em>stream</em>.');
546         $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.');
547         $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '</p>';
548
549         $private_wall = get_pconfig(local_user(),'facebook','private_wall');
550         $checked = (($private_wall) ? ' checked="checked" ' : '');
551         $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 ;
552
553
554         $no_wall = get_pconfig(local_user(),'facebook','no_wall');
555         $checked = (($no_wall) ? ' checked="checked" ' : '');
556         $o .= '<input type="checkbox" name="facebook_no_wall" value="1"' . $checked . '/>' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ;
557
558         $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>';
559
560
561         $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps');
562
563         $o .= '<div><label id="blocked-apps-label" for="blocked-apps">' . t('Comma separated applications to ignore') . ' </label></div>';
564         $o .= '<div><textarea id="blocked-apps" name="blocked_apps" >' . htmlspecialchars($blocked_apps) . '</textarea></div>';
565
566         $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form></div>';
567     }
568
569     return $o;
570 }
571
572
573
574 function facebook_cron($a,$b) {
575
576     $last = get_config('facebook','last_poll');
577
578     $poll_interval = intval(get_config('facebook','poll_interval'));
579     if(! $poll_interval)
580         $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL;
581
582     if($last) {
583         $next = $last + $poll_interval;
584         if($next > time())
585             return;
586     }
587
588     logger('facebook_cron');
589
590
591     // Find the FB users on this site and randomize in case one of them
592     // uses an obscene amount of memory. It may kill this queue run
593     // but hopefully we'll get a few others through on each run.
594
595     $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() ");
596     if(count($r)) {
597         foreach($r as $rr) {
598             if(get_pconfig($rr['uid'],'facebook','no_linking'))
599                 continue;
600             $ab = intval(get_config('system','account_abandon_days'));
601             if($ab > 0) {
602                 $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1",
603                     intval($rr['uid']),
604                     intval($ab)
605                 );
606                 if(! count($z))
607                     continue;
608             }
609
610             // check for new friends once a day
611             $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check');
612             if($last_friend_check)
613                 $next_friend_check = $last_friend_check + 86400;
614             if($next_friend_check <= time()) {
615                 fb_get_friends($rr['uid'], true);
616                 set_pconfig($rr['uid'],'facebook','friend_check',time());
617             }
618             fb_consume_all($rr['uid']);
619         }
620     }
621
622     if (get_config('facebook', 'realtime_active') == 1) {
623         if (!facebook_check_realtime_active()) {
624
625             logger('facebook_cron: Facebook is not sending Real-Time Updates any more, although it is supposed to. Trying to fix it...', LOGGER_NORMAL);
626             facebook_subscription_add_users();
627
628             if (facebook_check_realtime_active())
629                 logger('facebook_cron: Successful', LOGGER_NORMAL);
630             else {
631                 logger('facebook_cron: Failed', LOGGER_NORMAL);
632
633                 if(strlen($a->config['admin_email']) && !get_config('facebook', 'realtime_err_mailsent')) {
634                     $res = mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'),
635                         "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.",
636                         'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n"
637                             . 'Content-type: text/plain; charset=UTF-8' . "\n"
638                             . 'Content-transfer-encoding: 8bit'
639                     );
640
641                     set_config('facebook', 'realtime_err_mailsent', 1);
642                 }
643             }
644         } else { // !facebook_check_realtime_active()
645             del_config('facebook', 'realtime_err_mailsent');
646         }
647     }
648
649     set_config('facebook','last_poll', time());
650
651 }
652
653
654
655 function facebook_plugin_settings(&$a,&$b) {
656
657     $b .= '<div class="settings-block">';
658     $b .= '<h3>' . t('Facebook') . '</h3>';
659     $b .= '<a href="facebook">' . t('Facebook Connector Settings') . '</a><br />';
660     $b .= '</div>';
661
662 }
663
664
665 function facebook_plugin_admin(&$a, &$o){
666     $o = '<input type="hidden" name="form_security_token" value="' . get_form_security_token("fbsave") . '">';
667
668     $o .= '<h4>' . t('Facebook API Key') . '</h4>';
669
670     $appid  = get_config('facebook', 'appid'  );
671     $appsecret = get_config('facebook', 'appsecret' );
672     $poll_interval = get_config('facebook', 'poll_interval' );
673     if (!$poll_interval) $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL;
674
675     $ret1 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appid' LIMIT 1");
676     $ret2 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appsecret' LIMIT 1");
677     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>');
678
679     $working_connection = false;
680     if ($appid && $appsecret) {
681         $subs = facebook_subscriptions_get();
682         if ($subs === null) $o .= t('Error: the given API Key seems to be incorrect (the application access token could not be retrieved).') . '<br>';
683         elseif (is_array($subs)) {
684             $o .= t('The given API Key seems to work correctly.') . '<br>';
685             $working_connection = true;
686         } else $o .= t('The correctness of the API Key could not be detected. Somthing strange\'s going on.') . '<br>';
687     }
688
689     $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;">';
690     $o .= '<label for="fb_appsecret">' . t('Application secret') . '</label><input name="appsecret" type="text" value="' . escape_tags($appsecret ? $appsecret : "") . '"><br style="clear: both;">';
691     $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;">';
692     $o .= '<input type="submit" name="fb_save_keys" value="' . t('Save') . '">';
693
694     if ($working_connection) {
695         $o .= '<h4>' . t('Real-Time Updates') . '</h4>';
696
697         $activated = facebook_check_realtime_active();
698         if ($activated) {
699             $o .= t('Real-Time Updates are activated.') . '<br><br>';
700             $o .= '<input type="submit" name="real_time_deactivate" value="' . t('Deactivate Real-Time Updates') . '">';
701         } else {
702             $o .= t('Real-Time Updates not activated.') . '<br><input type="submit" name="real_time_activate" value="' . t('Activate Real-Time Updates') . '">';
703         }
704     }
705 }
706
707 function facebook_plugin_admin_post(&$a, &$o){
708     check_form_security_token_redirectOnErr('/admin/plugins/facebook', 'fbsave');
709
710     if (x($_REQUEST,'fb_save_keys')) {
711         set_config('facebook', 'appid', $_REQUEST['appid']);
712         set_config('facebook', 'appsecret', $_REQUEST['appsecret']);
713         $poll_interval = IntVal($_REQUEST['poll_interval']);
714         if ($poll_interval >= FACEBOOK_MIN_POLL_INTERVAL) set_config('facebook', 'poll_interval', $poll_interval);
715         del_config('facebook', 'app_access_token');
716         info(t('The new values have been saved.'));
717     }
718     if (x($_REQUEST,'real_time_activate')) {
719         facebook_subscription_add_users();
720     }
721     if (x($_REQUEST,'real_time_deactivate')) {
722         facebook_subscription_del_users();
723     }
724 }
725
726 function facebook_jot_nets(&$a,&$b) {
727     if(! local_user())
728         return;
729
730     $fb_post = get_pconfig(local_user(),'facebook','post');
731     if(intval($fb_post) == 1) {
732         $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default');
733         $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : '');
734         $b .= '<div class="profile-jot-net"><input type="checkbox" name="facebook_enable"' . $selected . ' value="1" /> '
735             . t('Post to Facebook') . '</div>';
736     }
737 }
738
739
740 function facebook_post_hook(&$a,&$b) {
741
742
743     if($b['deleted'] || ($b['created'] !== $b['edited']))
744         return;
745
746     /**
747      * Post to Facebook stream
748      */
749
750     require_once('include/group.php');
751     require_once('include/html2plain.php');
752
753     logger('Facebook post');
754
755     $reply = false;
756     $likes = false;
757
758     $toplevel = (($b['id'] == $b['parent']) ? true : false);
759
760
761     $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1);
762
763     if((! $toplevel) && ($linking)) {
764         $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
765             intval($b['parent']),
766             intval($b['uid'])
767         );
768         if(count($r) && substr($r[0]['uri'],0,4) === 'fb::')
769             $reply = substr($r[0]['uri'],4);
770         elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::')
771             $reply = substr($r[0]['extid'],4);
772         else
773             return;
774
775         $u = q("SELECT * FROM user where uid = %d limit 1",
776             intval($b['uid'])
777         );
778         if(! count($u))
779             return;
780
781         // only accept comments from the item owner. Other contacts are unknown to FB.
782
783         if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname']))
784             return;
785
786
787         logger('facebook reply id=' . $reply);
788     }
789
790     if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) {
791
792         if($b['private'] && $reply === false) {
793             $allow_people = expand_acl($b['allow_cid']);
794             $allow_groups = expand_groups(expand_acl($b['allow_gid']));
795             $deny_people  = expand_acl($b['deny_cid']);
796             $deny_groups  = expand_groups(expand_acl($b['deny_gid']));
797
798             $recipients = array_unique(array_merge($allow_people,$allow_groups));
799             $deny = array_unique(array_merge($deny_people,$deny_groups));
800
801             $allow_str = dbesc(implode(', ',$recipients));
802             if($allow_str) {
803                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'");
804                 $allow_arr = array();
805                 if(count($r))
806                     foreach($r as $rr)
807                         $allow_arr[] = $rr['notify'];
808             }
809
810             $deny_str = dbesc(implode(', ',$deny));
811             if($deny_str) {
812                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'");
813                 $deny_arr = array();
814                 if(count($r))
815                     foreach($r as $rr)
816                         $deny_arr[] = $rr['notify'];
817             }
818
819             if(count($deny_arr) && (! count($allow_arr))) {
820
821                 // One or more FB folks were denied access but nobody on FB was specifically allowed access.
822                 // This might cause the post to be open to public on Facebook, but only to selected members
823                 // on another network. Since this could potentially leak a post to somebody who was denied,
824                 // we will skip posting it to Facebook with a slightly vague but relevant message that will
825                 // hopefully lead somebody to this code comment for a better explanation of what went wrong.
826
827                 notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL);
828                 return;
829             }
830
831
832             // if it's a private message but no Facebook members are allowed or denied, skip Facebook post
833
834             if((! count($allow_arr)) && (! count($deny_arr)))
835                 return;
836         }
837
838         if($b['verb'] == ACTIVITY_LIKE)
839             $likes = true;
840
841
842         $appid  = get_config('facebook', 'appid'  );
843         $secret = get_config('facebook', 'appsecret' );
844
845         if($appid && $secret) {
846
847             logger('facebook: have appid+secret');
848
849             $fb_token  = get_pconfig($b['uid'],'facebook','access_token');
850
851
852             // post to facebook if it's a public post and we've ticked the 'post to Facebook' box,
853             // or it's a private message with facebook participants
854             // or it's a reply or likes action to an existing facebook post
855
856             if($fb_token && ($toplevel || $b['private'] || $reply)) {
857                 logger('facebook: able to post');
858                 require_once('library/facebook.php');
859                 require_once('include/bbcode.php');
860
861                 $msg = $b['body'];
862
863                 logger('Facebook post: original msg=' . $msg, LOGGER_DATA);
864
865                 // make links readable before we strip the code
866
867                 // unless it's a dislike - just send the text as a comment
868
869                 if($b['verb'] == ACTIVITY_DISLIKE)
870                     $msg = trim(strip_tags(bbcode($msg)));
871
872                 // Old code
873                 /*$search_str = $a->get_baseurl() . '/search';
874
875                     if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
876
877                         // don't use hashtags for message link
878
879                         if(strpos($matches[2],$search_str) === false) {
880                             $link = $matches[1];
881                             if(substr($matches[2],0,5) != '[img]')
882                                 $linkname = $matches[2];
883                         }
884                     }
885
886                     // strip tag links to avoid link clutter, this really should be
887                     // configurable because we're losing information
888
889                     $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
890
891                     // provide the link separately for normal links
892                     $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
893
894                     if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
895                         $image = $matches[1];
896
897                     $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
898
899                     if((strpos($link,z_root()) !== false) && (! $image))
900                         $image = $a->get_baseurl() . '/images/friendica-64.jpg';
901
902                     $msg = trim(strip_tags(bbcode($msg)));*/
903
904                 // New code
905
906                 // Looking for the first image
907                 $image = '';
908                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches))
909                     $image = $matches[3];
910
911                 if ($image != '')
912                     if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches))
913                         $image = $matches[1];
914
915                 // Checking for a bookmark element
916                 $body = $b['body'];
917                 if (strpos($body, "[bookmark") !== false) {
918                     // splitting the text in two parts:
919                     // before and after the bookmark
920                     $pos = strpos($body, "[bookmark");
921                     $body1 = substr($body, 0, $pos);
922                     $body2 = substr($body, $pos);
923
924                     // Removing the bookmark and all quotes after the bookmark
925                     // they are mostly only the content after the bookmark.
926                     $body2 = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism",'',$body2);
927                     $body2 = preg_replace("/\[quote\=([^\]]*)\](.*?)\[\/quote\]/ism",'',$body2);
928                     $body2 = preg_replace("/\[quote\](.*?)\[\/quote\]/ism",'',$body2);
929
930                     $body = $body1.$body2;
931                 }
932
933                 // At first convert the text to html
934                 $html = bbcode($body);
935
936                 // Then convert it to plain text
937                 $msg = trim($b['title']." \n\n".html2plain($html, 0, true));
938                 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
939
940                 // Removing multiple newlines
941                 while (strpos($msg, "\n\n\n") !== false)
942                     $msg = str_replace("\n\n\n", "\n\n", $msg);
943
944                 // add any attachments as text urls
945                 $arr = explode(',',$b['attach']);
946
947                 if(count($arr)) {
948                     $msg .= "\n";
949                     foreach($arr as $r) {
950                         $matches = false;
951                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
952                         if($cnt) {
953                             $msg .= "\n".$matches[1];
954                         }
955                     }
956                 }
957
958                 $link = '';
959                 $linkname = '';
960                 // look for bookmark-bbcode and handle it with priority
961                 if(preg_match("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/is",$b['body'],$matches)) {
962                     $link = $matches[1];
963                     $linkname = $matches[2];
964                 }
965
966                 // If there is no bookmark element then take the first link
967                 if ($link == '') {
968                     $links = collecturls($html);
969                     if (sizeof($links) > 0) {
970                         reset($links);
971                         $link = current($links);
972                     }
973                 }
974
975                 // Remove trailing and leading spaces
976                 $msg = trim($msg);
977
978                 // Since facebook increased the maxpostlen massively this never should happen again :)
979                 if (strlen($msg) > FACEBOOK_MAXPOSTLEN) {
980                     $shortlink = "";
981                     require_once('library/slinky.php');
982
983                     $display_url = $b['plink'];
984
985                     $slinky = new Slinky( $display_url );
986                     // setup a cascade of shortening services
987                     // try to get a short link from these services
988                     // in the order ur1.ca, trim, id.gd, tinyurl
989                     $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
990                     $shortlink = $slinky->short();
991                     // the new message will be shortened such that "... $shortlink"
992                     // will fit into the character limit
993                     $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
994                     $msg .= '... ' . $shortlink;
995                 }
996
997                 // Fallback - if message is empty
998                 if(!strlen($msg))
999                     $msg = $link;
1000
1001                 if(!strlen($msg))
1002                     $msg = $image;
1003
1004                 if(!strlen($msg))
1005                     $msg = $linkname;
1006
1007                 // If there is nothing to post then exit
1008                 if(!strlen($msg))
1009                     return;
1010
1011                 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
1012
1013                 if($likes) {
1014                     $postvars = array('access_token' => $fb_token);
1015                 }
1016                 else {
1017                     $postvars = array(
1018                         'access_token' => $fb_token,
1019                         'message' => $msg
1020                     );
1021                     if(isset($image))
1022                         $postvars['picture'] = $image;
1023                     if(isset($link))
1024                         $postvars['link'] = $link;
1025                     if(isset($linkname))
1026                         $postvars['name'] = $linkname;
1027                 }
1028
1029                 if(($b['private']) && ($toplevel)) {
1030                     $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
1031                     if(count($allow_arr))
1032                         $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
1033                     if(count($deny_arr))
1034                         $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
1035                     $postvars['privacy'] .= '}';
1036
1037                 }
1038
1039                 if($reply) {
1040                     $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
1041                 }
1042                 else {
1043                     $url = 'https://graph.facebook.com/me/feed';
1044                     if($b['plink'])
1045                         $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' .  $b['plink'] . '"}';
1046                 }
1047
1048                 logger('facebook: post to ' . $url);
1049                 logger('facebook: postvars: ' . print_r($postvars,true));
1050
1051                 // "test_mode" prevents anything from actually being posted.
1052                 // Otherwise, let's do it.
1053
1054                 if(! get_config('facebook','test_mode')) {
1055                     $x = post_url($url, $postvars);
1056                     logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
1057
1058                     $retj = json_decode($x);
1059                     if($retj->id) {
1060                         q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
1061                             dbesc('fb::' . $retj->id),
1062                             intval($b['id'])
1063                         );
1064                     }
1065                     else {
1066                         if(! $likes) {
1067                             $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
1068                             require_once('include/queue_fn.php');
1069                             add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
1070                             notice( t('Facebook post failed. Queued for retry.') . EOL);
1071                         }
1072
1073                         if (isset($retj->error) && $retj->error->type == "OAuthException" && $retj->error->code == 190) {
1074                             logger('Facebook session has expired due to changed password.', LOGGER_DEBUG);
1075
1076                             $last_notification = get_pconfig($b['uid'], 'facebook', 'session_expired_mailsent');
1077                             if (!$last_notification || $last_notification < (time() - FACEBOOK_SESSION_ERR_NOTIFICATION_INTERVAL)) {
1078                                 require_once('include/enotify.php');
1079
1080                                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($b['uid']) );
1081                                 notification(array(
1082                                     'uid' => $b['uid'],
1083                                     'type' => NOTIFY_SYSTEM,
1084                                     'system_type' => 'facebook_connection_invalid',
1085                                     'language'     => $r[0]['language'],
1086                                     'to_name'      => $r[0]['username'],
1087                                     'to_email'     => $r[0]['email'],
1088                                     'source_name'  => t('Administrator'),
1089                                     'source_link'  => $a->config["system"]["url"],
1090                                     'source_photo' => $a->config["system"]["url"] . '/images/person-80.jpg',
1091                                 ));
1092
1093                                 set_pconfig($b['uid'], 'facebook', 'session_expired_mailsent', time());
1094                             } else logger('Facebook: No notification, as the last one was sent on ' . $last_notification, LOGGER_DEBUG);
1095                         }
1096                     }
1097                 }
1098             }
1099         }
1100     }
1101 }
1102
1103 function facebook_enotify(&$app, &$data) {
1104     if (x($data, 'params') && $data['params']['type'] == NOTIFY_SYSTEM && x($data['params'], 'system_type') && $data['params']['system_type'] == 'facebook_connection_invalid') {
1105         $data['itemlink'] = '/facebook';
1106         $data['epreamble'] = $data['preamble'] = t('Your Facebook connection became invalid. Please Re-authenticate.');
1107         $data['subject'] = t('Facebook connection became invalid');
1108         $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]");
1109     }
1110 }
1111
1112 function facebook_post_local(&$a,&$b) {
1113
1114     // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
1115     // where we will discover it during background delivery.
1116
1117     // This can only be triggered by a local user posting to their own wall.
1118
1119     if((local_user()) && (local_user() == $b['uid'])) {
1120
1121         $fb_post   = intval(get_pconfig(local_user(),'facebook','post'));
1122         $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
1123
1124         // if API is used, default to the chosen settings
1125         if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
1126             $fb_enable = 1;
1127
1128         if(! $fb_enable)
1129             return;
1130
1131         if(strlen($b['postopts']))
1132             $b['postopts'] .= ',';
1133         $b['postopts'] .= 'facebook';
1134     }
1135 }
1136
1137
1138 function fb_queue_hook(&$a,&$b) {
1139
1140     $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
1141         dbesc(NETWORK_FACEBOOK)
1142     );
1143     if(! count($qi))
1144         return;
1145
1146     require_once('include/queue_fn.php');
1147
1148     foreach($qi as $x) {
1149         if($x['network'] !== NETWORK_FACEBOOK)
1150             continue;
1151
1152         logger('facebook_queue: run');
1153
1154         $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid`
1155                         WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
1156             intval($x['cid'])
1157         );
1158         if(! count($r))
1159             continue;
1160
1161         $user = $r[0];
1162
1163         $appid  = get_config('facebook', 'appid'  );
1164         $secret = get_config('facebook', 'appsecret' );
1165
1166         if($appid && $secret) {
1167             $fb_post   = intval(get_pconfig($user['uid'],'facebook','post'));
1168             $fb_token  = get_pconfig($user['uid'],'facebook','access_token');
1169
1170             if($fb_post && $fb_token) {
1171                 logger('facebook_queue: able to post');
1172                 require_once('library/facebook.php');
1173
1174                 $z = unserialize($x['content']);
1175                 $item = $z['item'];
1176                 $j = post_url($z['url'],$z['post']);
1177
1178                 $retj = json_decode($j);
1179                 if($retj->id) {
1180                     q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
1181                         dbesc('fb::' . $retj->id),
1182                         intval($item)
1183                     );
1184                     logger('facebook_queue: success: ' . $j);
1185                     remove_queue_item($x['id']);
1186                 }
1187                 else {
1188                     logger('facebook_queue: failed: ' . $j);
1189                     update_queue_time($x['id']);
1190                 }
1191             }
1192         }
1193     }
1194 }
1195
1196 function fb_get_timeline($access_token, &$since) {
1197     $entries = new stdClass();
1198     $entries->data = array();
1199     $newest = 0;
1200
1201     $url = 'https://graph.facebook.com/me/home?access_token='.$access_token;
1202
1203     if ($since != 0)
1204         $url .= "&since=".$since;
1205
1206     do {
1207         $s = fetch_url($url);
1208         $j = json_decode($s);
1209         $oldestdate = time();
1210         if (isset($j->data))
1211             foreach ($j->data as $entry) {
1212                 $created = strtotime($entry->created_time);
1213
1214                 if ($newest < $created)
1215                     $newest = $created;
1216
1217                 if ($created >= $since)
1218                     $entries->data[] = $entry;
1219
1220                 if ($created <= $oldestdate)
1221                     $oldestdate = $created;
1222             }
1223         else
1224             break;
1225
1226         $url = $j->paging->next;
1227
1228     } while (($oldestdate > $since) and ($since != 0) and ($url != ''));
1229
1230     if ($newest > $since)
1231         $since = $newest;
1232
1233     return($entries);
1234 }
1235
1236 function fb_consume_all($uid) {
1237
1238     require_once('include/items.php');
1239
1240     $access_token = get_pconfig($uid,'facebook','access_token');
1241     if(! $access_token)
1242         return;
1243
1244     if(! get_pconfig($uid,'facebook','no_wall')) {
1245         $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
1246         $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
1247         if($s) {
1248             $j = json_decode($s);
1249             if (isset($j->data)) {
1250                 logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
1251                 fb_consume_stream($uid,$j,($private_wall) ? false : true);
1252             } else {
1253                 logger('fb_consume_stream: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1254             }
1255         }
1256     }
1257     // Get the last date
1258     $lastdate = get_pconfig($uid,'facebook','lastdate');
1259     // fetch all items since the last date
1260     $j = fb_get_timeline($access_token, &$lastdate);
1261     if (isset($j->data)) {
1262         logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
1263         fb_consume_stream($uid,$j,false);
1264
1265         // Write back the last date
1266         set_pconfig($uid,'facebook','lastdate', $lastdate);
1267     } else
1268         logger('fb_consume_stream: feed: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1269 }
1270
1271 function fb_get_photo($uid,$link) {
1272     $access_token = get_pconfig($uid,'facebook','access_token');
1273     if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
1274         return "";
1275     //return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1276     $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
1277     if($ret)
1278         $photo_id = $match[1];
1279     $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
1280     $j = json_decode($x);
1281     if($j->picture)
1282         return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
1283     //else
1284     //  return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1285 }
1286
1287 function fb_consume_stream($uid,$j,$wall = false) {
1288
1289     $a = get_app();
1290
1291
1292     $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
1293         intval($uid)
1294     );
1295     if(! count($user))
1296         return;
1297
1298     $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1299
1300     $no_linking = get_pconfig($uid,'facebook','no_linking');
1301     if($no_linking)
1302         return;
1303
1304     $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1305         intval($uid)
1306     );
1307
1308     $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
1309     $blocked_apps_arr = explode(',',$blocked_apps);
1310
1311     $self_id = get_pconfig($uid,'facebook','self_id');
1312     if(! count($j->data) || (! strlen($self_id)))
1313         return;
1314
1315     foreach($j->data as $entry) {
1316         logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
1317         $datarray = array();
1318
1319         $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
1320             dbesc('fb::' . $entry->id),
1321             dbesc('fb::' . $entry->id),
1322             intval($uid)
1323         );
1324         if(count($r)) {
1325             $post_exists = true;
1326             $orig_post = $r[0];
1327             $top_item = $r[0]['id'];
1328         }
1329         else {
1330             $post_exists = false;
1331             $orig_post = null;
1332         }
1333
1334         if(! $orig_post) {
1335             $datarray['gravity'] = 0;
1336             $datarray['uid'] = $uid;
1337             $datarray['wall'] = (($wall) ? 1 : 0);
1338             $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
1339             $from = $entry->from;
1340             if($from->id == $self_id)
1341                 $datarray['contact-id'] = $self[0]['id'];
1342             else {
1343                 // Looking if user is known - if not he is added
1344                 $access_token = get_pconfig($uid, 'facebook', 'access_token');
1345                 fb_get_friends_sync_new($uid, $access_token, $from);
1346
1347                 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1348                     dbesc($from->id),
1349                     intval($uid)
1350                 );
1351                 if(count($r))
1352                     $datarray['contact-id'] = $r[0]['id'];
1353             }
1354
1355             // don't store post if we don't have a contact
1356             if(! x($datarray,'contact-id')) {
1357                 logger('facebook: no contact '.$from->name.' '.$from->id.'. post ignored');
1358                 continue;
1359             }
1360
1361             $datarray['verb'] = ACTIVITY_POST;
1362             if($wall) {
1363                 $datarray['owner-name'] = $self[0]['name'];
1364                 $datarray['owner-link'] = $self[0]['url'];
1365                 $datarray['owner-avatar'] = $self[0]['thumb'];
1366             }
1367             if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
1368                 $datarray['app'] = strip_tags($entry->application->name);
1369             else
1370                 $datarray['app'] = 'facebook';
1371
1372             $found_blocked = false;
1373
1374             if(count($blocked_apps_arr)) {
1375                 foreach($blocked_apps_arr as $bad_appl) {
1376                     if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
1377                         $found_blocked = true;
1378                     }
1379                 }
1380             }
1381
1382             if($found_blocked) {
1383                 logger('facebook: blocking application: ' . $datarray['app']);
1384                 continue;
1385             }
1386
1387             $datarray['author-name'] = $from->name;
1388             $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
1389             $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
1390             $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
1391
1392             logger('facebook: post '.$entry->id.' from '.$from->name);
1393
1394             $datarray['body'] = escape_tags($entry->message);
1395
1396             if($entry->name and $entry->link)
1397                 $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->name."[/bookmark]";
1398             elseif ($entry->name)
1399                 $datarray['body'] .= "\n\n[b]" . $entry->name."[/b]";
1400
1401             if($entry->caption) {
1402                 if(!$entry->name and $entry->link)
1403                     $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->caption."[/bookmark]";
1404                 else
1405                     $datarray['body'] .= "[i]" . $entry->caption."[/i]\n";
1406             }
1407
1408             if(!$entry->caption and !$entry->name) {
1409                 if ($entry->link)
1410                     $datarray['body'] .= "\n[url]".$entry->link."[/url]\n";
1411                 else
1412                     $datarray['body'] .= "\n";
1413             }
1414
1415             $quote = "";
1416             if($entry->description)
1417                 $quote = $entry->description;
1418
1419             if ($entry->properties)
1420                 foreach ($entry->properties as $property)
1421                     $quote .= "\n".$property->name.": [url=".$property->href."]".$property->text."[/url]";
1422
1423             if ($quote)
1424                 $datarray['body'] .= "\n[quote]".$quote."[/quote]";
1425
1426             // Only import the picture when the message is no video
1427             // oembed display a picture of the video as well
1428             if ($entry->type != "video") {
1429                 if($entry->picture && $entry->link) {
1430                     $datarray['body'] .= "\n" . '[url=' . $entry->link . '][img]'.$entry->picture.'[/img][/url]';
1431                 }
1432                 else {
1433                     if($entry->picture)
1434                         $datarray['body'] .= "\n" . '[img]' . $entry->picture . '[/img]';
1435                     // if just a link, it may be a wall photo - check
1436                     if($entry->link)
1437                         $datarray['body'] .= fb_get_photo($uid,$entry->link);
1438                 }
1439             }
1440
1441             // Just as a test - to see if these are the missing entries
1442             //if(trim($datarray['body']) == '')
1443             //  $datarray['body'] = $entry->story;
1444
1445             if(trim($datarray['body']) == '') {
1446                 logger('facebook: empty body '.$entry->id.' '.print_r($entry, true));
1447                 continue;
1448             }
1449
1450             $datarray['body'] .= "\n";
1451
1452             if ($entry->icon)
1453                 $datarray['body'] .= "[img]".$entry->icon."[/img] &nbsp; ";
1454
1455             if ($entry->actions)
1456                 foreach ($entry->actions as $action)
1457                     if (($action->name != "Comment") and ($action->name != "Like"))
1458                         $datarray['body'] .= "[url=".$action->link."]".$action->name."[/url] &nbsp; ";
1459
1460             $datarray['body'] = trim($datarray['body']);
1461
1462             //if(($datarray['body'] != '') and ($uid == 1))
1463             //  $datarray['body'] .= "[noparse]".print_r($entry, true)."[/noparse]";
1464
1465             if ($entry->place->name)
1466                 $datarray['coord'] = $entry->place->name;
1467             else if ($entry->place->location->street or $entry->place->location->city or $entry->place->location->Denmark) {
1468                 if ($entry->place->location->street)
1469                     $datarray['coord'] = $entry->place->location->street;
1470                 if ($entry->place->location->city)
1471                     $datarray['coord'] .= " ".$entry->place->location->city;
1472                 if ($entry->place->location->country)
1473                     $datarray['coord'] .= " ".$entry->place->location->country;
1474             } else if ($entry->place->location->latitude and $entry->place->location->longitude)
1475                 $datarray['coord'] = substr($entry->place->location->latitude, 0, 8)
1476                     .' '.substr($entry->place->location->longitude, 0, 8);
1477
1478             $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1479             $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1480
1481             // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1482             // as the identities are from a foreign system. Mark it as private to the owner.
1483
1484             if($entry->privacy && $entry->privacy->value !== 'EVERYONE') {
1485                 $datarray['private'] = 1;
1486                 $datarray['allow_cid'] = '<' . $self[0]['id'] . '>';
1487             }
1488
1489             $top_item = item_store($datarray);
1490             $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1491                 intval($top_item),
1492                 intval($uid)
1493             );
1494             if(count($r)) {
1495                 $orig_post = $r[0];
1496                 logger('fb: new top level item posted');
1497             }
1498         }
1499
1500         if(isset($entry->likes) && isset($entry->likes->data))
1501             $likers = $entry->likes->data;
1502         else
1503             $likers = null;
1504
1505         if(isset($entry->comments) && isset($entry->comments->data))
1506             $comments = $entry->comments->data;
1507         else
1508             $comments = null;
1509
1510         if(is_array($likers)) {
1511             foreach($likers as $likes) {
1512
1513                 if(! $orig_post)
1514                     continue;
1515
1516                 // If we posted the like locally, it will be found with our url, not the FB url.
1517
1518                 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id);
1519
1520                 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s'
1521                                         AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1522                     dbesc($orig_post['uri']),
1523                     intval($uid),
1524                     dbesc(ACTIVITY_LIKE),
1525                     dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1526                     dbesc($second_url)
1527                 );
1528
1529                 if(count($r))
1530                     continue;
1531
1532                 $likedata = array();
1533                 $likedata['parent'] = $top_item;
1534                 $likedata['verb'] = ACTIVITY_LIKE;
1535                 $likedata['gravity'] = 3;
1536                 $likedata['uid'] = $uid;
1537                 $likedata['wall'] = (($wall) ? 1 : 0);
1538                 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1539                 $likedata['parent-uri'] = $orig_post['uri'];
1540                 if($likes->id == $self_id)
1541                     $likedata['contact-id'] = $self[0]['id'];
1542                 else {
1543                     $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1544                         dbesc($likes->id),
1545                         intval($uid)
1546                     );
1547                     if(count($r))
1548                         $likedata['contact-id'] = $r[0]['id'];
1549                 }
1550                 if(! x($likedata,'contact-id'))
1551                     $likedata['contact-id'] = $orig_post['contact-id'];
1552
1553                 $likedata['app'] = 'facebook';
1554                 $likedata['verb'] = ACTIVITY_LIKE;
1555                 $likedata['author-name'] = $likes->name;
1556                 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1557                 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1558
1559                 $author  = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1560                 $objauthor =  '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1561                 $post_type = t('status');
1562                 $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1563                 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1564
1565                 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1566                 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' .
1567                     '<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>';
1568
1569                 $item = item_store($likedata);
1570             }
1571         }
1572         if(is_array($comments)) {
1573             foreach($comments as $cmnt) {
1574
1575                 if(! $orig_post)
1576                     continue;
1577
1578                 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1579                     intval($uid),
1580                     dbesc('fb::' . $cmnt->id),
1581                     dbesc('fb::' . $cmnt->id)
1582                 );
1583                 if(count($r))
1584                     continue;
1585
1586                 $cmntdata = array();
1587                 $cmntdata['parent'] = $top_item;
1588                 $cmntdata['verb'] = ACTIVITY_POST;
1589                 $cmntdata['gravity'] = 6;
1590                 $cmntdata['uid'] = $uid;
1591                 $cmntdata['wall'] = (($wall) ? 1 : 0);
1592                 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1593                 $cmntdata['parent-uri'] = $orig_post['uri'];
1594                 if($cmnt->from->id == $self_id) {
1595                     $cmntdata['contact-id'] = $self[0]['id'];
1596                 }
1597                 else {
1598                     $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1599                         dbesc($cmnt->from->id),
1600                         intval($uid)
1601                     );
1602                     if(count($r)) {
1603                         $cmntdata['contact-id'] = $r[0]['id'];
1604                         if($r[0]['blocked'] || $r[0]['readonly'])
1605                             continue;
1606                     }
1607                 }
1608                 if(! x($cmntdata,'contact-id'))
1609                     $cmntdata['contact-id'] = $orig_post['contact-id'];
1610
1611                 $cmntdata['app'] = 'facebook';
1612                 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1613                 $cmntdata['edited']  = datetime_convert('UTC','UTC',$cmnt->created_time);
1614                 $cmntdata['verb'] = ACTIVITY_POST;
1615                 $cmntdata['author-name'] = $cmnt->from->name;
1616                 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1617                 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1618                 $cmntdata['body'] = $cmnt->message;
1619                 $item = item_store($cmntdata);
1620
1621                 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ",
1622                     dbesc($orig_post['uri']),
1623                     intval($uid)
1624                 );
1625
1626                 if(count($myconv)) {
1627                     $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1628
1629                     foreach($myconv as $conv) {
1630
1631                         // now if we find a match, it means we're in this conversation
1632
1633                         if(! link_compare($conv['author-link'],$importer_url))
1634                             continue;
1635
1636                         require_once('include/enotify.php');
1637
1638                         $conv_parent = $conv['parent'];
1639
1640                         notification(array(
1641                             'type'         => NOTIFY_COMMENT,
1642                             'notify_flags' => $user[0]['notify-flags'],
1643                             'language'     => $user[0]['language'],
1644                             'to_name'      => $user[0]['username'],
1645                             'to_email'     => $user[0]['email'],
1646                             'uid'          => $user[0]['uid'],
1647                             'item'         => $cmntdata,
1648                             'link'                 => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $item,
1649                             'source_name'  => $cmntdata['author-name'],
1650                             'source_link'  => $cmntdata['author-link'],
1651                             'source_photo' => $cmntdata['author-avatar'],
1652                             'verb'         => ACTIVITY_POST,
1653                             'otype'        => 'item',
1654                             'parent'       => $conv_parent,
1655                         ));
1656
1657                         // only send one notification
1658                         break;
1659                     }
1660                 }
1661             }
1662         }
1663     }
1664 }
1665
1666
1667 function fb_get_app_access_token() {
1668
1669     $acc_token = get_config('facebook','app_access_token');
1670
1671     if ($acc_token !== false) return $acc_token;
1672
1673     $appid = get_config('facebook','appid');
1674     $appsecret = get_config('facebook', 'appsecret');
1675
1676     if ($appid === false || $appsecret === false) {
1677         logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG);
1678         return false;
1679     }
1680     logger('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials', LOGGER_DATA);
1681     $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials');
1682
1683     if(strpos($x,'access_token=') !== false) {
1684         logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA);
1685
1686         $token = str_replace('access_token=', '', $x);
1687         if(strpos($token,'&') !== false)
1688             $token = substr($token,0,strpos($token,'&'));
1689
1690         if ($token == "") {
1691             logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG);
1692             return false;
1693         }
1694         set_config('facebook','app_access_token',$token);
1695         return $token;
1696     } else {
1697         logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA);
1698         return false;
1699     }
1700 }
1701
1702 function facebook_subscription_del_users() {
1703     $a = get_app();
1704     $access_token = fb_get_app_access_token();
1705
1706     $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1707     facebook_delete_url($url);
1708
1709     if (!facebook_check_realtime_active()) del_config('facebook', 'realtime_active');
1710 }
1711
1712 function facebook_subscription_add_users($second_try = false) {
1713     $a = get_app();
1714     $access_token = fb_get_app_access_token();
1715
1716     $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1717
1718     list($usec, $sec) = explode(" ", microtime());
1719     $verify_token = sha1($usec . $sec . rand(0, 999999999));
1720     set_config('facebook', 'cb_verify_token', $verify_token);
1721
1722     $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1';
1723
1724     $j = post_url($url,array(
1725         "object" => "user",
1726         "fields" => "feed,friends",
1727         "callback_url" => $cb,
1728         "verify_token" => $verify_token,
1729     ));
1730     del_config('facebook', 'cb_verify_token');
1731
1732     if ($j) {
1733         $x = json_decode($j);
1734         logger("Facebook reponse: " . $j, LOGGER_DATA);
1735         if (isset($x->error)) {
1736             logger('facebook_subscription_add_users: got an error: ' . $j);
1737             if ($x->error->type == "OAuthException" && $x->error->code == 190) {
1738                 del_config('facebook', 'app_access_token');
1739                 if ($second_try === false) facebook_subscription_add_users(true);
1740             }
1741         } else {
1742             logger('facebook_subscription_add_users: sucessful');
1743             if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1);
1744         }
1745     };
1746 }
1747
1748 function facebook_subscriptions_get() {
1749
1750     $access_token = fb_get_app_access_token();
1751     if (!$access_token) return null;
1752
1753     $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1754     $j = fetch_url($url);
1755     $ret = null;
1756     if ($j) {
1757         $x = json_decode($j);
1758         if (isset($x->data)) $ret = $x->data;
1759     }
1760     return $ret;
1761 }
1762
1763
1764 function facebook_check_realtime_active() {
1765     $ret = facebook_subscriptions_get();
1766     if (is_null($ret)) return false;
1767     if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true;
1768     return false;
1769 }
1770
1771
1772
1773
1774 // DELETE-request to $url
1775
1776 if(! function_exists('facebook_delete_url')) {
1777     function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) {
1778         $a = get_app();
1779         $ch = curl_init($url);
1780         if(($redirects > 8) || (! $ch))
1781             return false;
1782
1783         curl_setopt($ch, CURLOPT_HEADER, true);
1784         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
1785         curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
1786         curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
1787
1788         if(intval($timeout)) {
1789             curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1790         }
1791         else {
1792             $curl_time = intval(get_config('system','curl_timeout'));
1793             curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
1794         }
1795
1796         if(defined('LIGHTTPD')) {
1797             if(!is_array($headers)) {
1798                 $headers = array('Expect:');
1799             } else {
1800                 if(!in_array('Expect:', $headers)) {
1801                     array_push($headers, 'Expect:');
1802                 }
1803             }
1804         }
1805         if($headers)
1806             curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1807
1808         $check_cert = get_config('system','verifyssl');
1809         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
1810         $prx = get_config('system','proxy');
1811         if(strlen($prx)) {
1812             curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
1813             curl_setopt($ch, CURLOPT_PROXY, $prx);
1814             $prxusr = get_config('system','proxyuser');
1815             if(strlen($prxusr))
1816                 curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
1817         }
1818
1819         $a->set_curl_code(0);
1820
1821         // don't let curl abort the entire application
1822         // if it throws any errors.
1823
1824         $s = @curl_exec($ch);
1825
1826         $base = $s;
1827         $curl_info = curl_getinfo($ch);
1828         $http_code = $curl_info['http_code'];
1829
1830         $header = '';
1831
1832         // Pull out multiple headers, e.g. proxy and continuation headers
1833         // allow for HTTP/2.x without fixing code
1834
1835         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
1836             $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
1837             $header .= $chunk;
1838             $base = substr($base,strlen($chunk));
1839         }
1840
1841         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
1842             $matches = array();
1843             preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
1844             $url = trim(array_pop($matches));
1845             $url_parsed = @parse_url($url);
1846             if (isset($url_parsed)) {
1847                 $redirects++;
1848                 return delete_url($url,$headers,$redirects,$timeout);
1849             }
1850         }
1851         $a->set_curl_code($http_code);
1852         $body = substr($s,strlen($header));
1853
1854         $a->set_curl_headers($header);
1855
1856         curl_close($ch);
1857         return($body);
1858     }}