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