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