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