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