]> git.mxchange.org Git - friendica-addons.git/blob - facebook/facebook.php
Facebook: detecting of images when exporting
[friendica-addons.git] / facebook / facebook.php
1 <?php
2 /**
3  * Name: Facebook Connector
4  * Version: 1.1
5  * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
6  *         Tobias Hößl <https://github.com/CatoTH/>
7  */
8
9 /**
10  * Installing the Friendica/Facebook connector
11  *
12  * 1. register an API key for your site from developer.facebook.com
13  *   a. We'd be very happy if you include "Friendica" in the application name
14  *      to increase name recognition. The Friendica icons are also present
15  *      in the images directory and may be uploaded as a Facebook app icon.
16  *      Use images/friendica-16.jpg for the Icon and images/friendica-128.jpg for the Logo.
17  *   b. The url should be your site URL with a trailing slash.
18  *      Friendica is a software application and does not require a Privacy Policy 
19  *      or Terms of Service, though your installation of it might. Facebook may require
20  *      that you provide a Privacy Policy, which we find ironic.  
21  *   c. Set the following values in your .htconfig.php file
22  *         $a->config['facebook']['appid'] = 'xxxxxxxxxxx';
23  *         $a->config['facebook']['appsecret'] = 'xxxxxxxxxxxxxxx';
24  *      Replace with the settings Facebook gives you.
25  *   d. Navigate to Set Web->Site URL & Domain -> Website Settings.  Set 
26  *      Site URL to yoursubdomain.yourdomain.com. Set Site Domain to your 
27  *      yourdomain.com.
28  * 2. (This step is now obsolete. Enable the plugin via the Admin panel.)
29  *     Enable the facebook plugin by including it in .htconfig.php - e.g. 
30  *     $a->config['system']['addon'] = 'plugin1,plugin2,facebook';
31  * 3. Visit the Facebook Settings section of the "Settings->Plugin Settings" page.
32  *    and click 'Install Facebook Connector'.
33  * 4. This will ask you to login to Facebook and grant permission to the 
34  *    plugin to do its stuff. Allow it to do so. 
35  * 5. Optional step: If you want to use Facebook Real Time Updates (so new messages
36  *    and new contacts are added ~1min after they are postet / added on FB), go to
37  *    Settings -> plugins -> facebook and press the "Activate Real-Time Updates"-button.
38  * 6. You're done. To turn it off visit the Plugin Settings page again and
39  *    'Remove Facebook posting'.
40  *
41  * Vidoes and embeds will not be posted if there is no other content. Links 
42  * and images will be converted to a format suitable for the Facebook API and 
43  * long posts truncated - with a link to view the full post. 
44  *
45  * Facebook contacts will not be able to view private photos, as they are not able to
46  * authenticate to your site to establish identity. We will address this 
47  * in a future release.
48  */
49
50 define('FACEBOOK_MAXPOSTLEN', 420);
51
52
53 function facebook_install() {
54         register_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
55         register_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
56         register_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
57         register_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
58         register_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
59         register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
60         
61         if (get_config('facebook', 'realtime_active') == 1) facebook_subscription_add_users(); // Restore settings, if the plugin was installed before
62 }
63
64
65 function facebook_uninstall() {
66         unregister_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
67         unregister_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
68         unregister_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
69         unregister_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
70         unregister_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
71         unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
72
73         // hook moved
74         unregister_hook('post_local_end',  'addon/facebook/facebook.php', 'facebook_post_hook');
75         unregister_hook('plugin_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
76         
77         if (get_config('facebook', 'realtime_active') == 1) facebook_subscription_del_users();
78 }
79
80
81 /* declare the facebook_module function so that /facebook url requests will land here */
82
83 function facebook_module() {}
84
85
86
87 // If a->argv[1] is a nickname, this is a callback from Facebook oauth requests.
88 // If $_REQUEST["realtime_cb"] is set, this is a callback from the Real-Time Updates API
89
90 function facebook_init(&$a) {
91         
92         if (x($_REQUEST, "realtime_cb") && x($_REQUEST, "realtime_cb")) {
93                 logger("facebook_init: Facebook Real-Time callback called", LOGGER_DEBUG);
94                 
95                 if (x($_REQUEST, "hub_verify_token")) {
96                         // this is the verification callback while registering for real time updates
97                         
98                         $verify_token = get_config('facebook', 'cb_verify_token');
99                         if ($verify_token != $_REQUEST["hub_verify_token"]) {
100                                 logger('facebook_init: Wrong Facebook Callback Verifier - expected ' . $verify_token . ', got ' . $_REQUEST["hub_verify_token"]);
101                                 return;
102                         }
103                         
104                         if (x($_REQUEST, "hub_challenge")) {
105                                 logger('facebook_init: Answering Challenge: ' . $_REQUEST["hub_challenge"], LOGGER_DATA);
106                                 echo $_REQUEST["hub_challenge"];
107                                 die();
108                         }
109                 }
110                 
111                 require_once('include/items.php');
112                 
113                 // this is a status update
114                 $content = file_get_contents("php://input");
115                 if (is_numeric($content)) $content = file_get_contents("php://input");
116                 $js = json_decode($content);
117                 logger(print_r($js, true), LOGGER_DATA);
118                 
119                 if (!isset($js->object) || $js->object != "user" || !isset($js->entry)) {
120                         logger('facebook_init: Could not parse Real-Time Update data', LOGGER_DEBUG);
121                         return;
122                 }
123                 
124                 $affected_users = array("feed" => array(), "friends" => array());
125                 
126                 foreach ($js->entry as $entry) {
127                         $fbuser = $entry->uid;
128                         foreach ($entry->changed_fields as $field) {
129                                 if (!isset($affected_users[$field])) {
130                                         logger('facebook_init: Unknown field "' . $field . '"');
131                                         continue;
132                                 }
133                                 if (in_array($fbuser, $affected_users[$field])) continue;
134                                 
135                                 $r = q("SELECT `uid` FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'self_id' AND `v` = '%s' LIMIT 1", dbesc($fbuser));
136                                 if(! count($r))
137                                         continue;
138                                 $uid = $r[0]['uid'];
139                                 
140                                 $access_token = get_pconfig($uid,'facebook','access_token');
141                                 if(! $access_token)
142                                         return;
143                                 
144                                 switch ($field) {
145                                         case "feed":
146                                                 logger('facebook_init: FB-User ' . $fbuser . ' / feed', LOGGER_DEBUG);
147                                                 
148                                                 if(! get_pconfig($uid,'facebook','no_wall')) {
149                                                         $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
150                                                         $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
151                                                         if($s) {
152                                                                 $j = json_decode($s);
153                                                                 logger('facebook_init: wall: ' . print_r($j,true), LOGGER_DATA);
154                                                                 fb_consume_stream($uid,$j,($private_wall) ? false : true);
155                                                         }
156                                                 }
157                                                 
158                                         break;
159                                         case "friends":
160                                                 logger('facebook_init: FB-User ' . $fbuser . ' / friends', LOGGER_DEBUG);
161                                                 
162                                                 fb_get_friends($uid, false);
163                                                 set_pconfig($uid,'facebook','friend_check',time());
164                                         break;
165                                         default:
166                                                 logger('facebook_init: Unknown callback field for ' . $fbuser, LOGGER_NORMAL);
167                                 }
168                                 $affected_users[$field][] = $fbuser;
169                         }
170                 }
171         }
172
173         
174         if($a->argc != 2)
175                 return;
176         $nick = $a->argv[1];
177         if(strlen($nick))
178                 $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1",
179                                 dbesc($nick)
180                 );
181         if(! count($r))
182                 return;
183
184         $uid           = $r[0]['uid'];
185         $auth_code     = (x($_GET, 'code') ? $_GET['code'] : '');
186         $error         = (x($_GET, 'error_description') ? $_GET['error_description'] : '');
187
188
189         if($error)
190                 logger('facebook_init: Error: ' . $error);
191
192         if($auth_code && $uid) {
193
194                 $appid = get_config('facebook','appid');
195                 $appsecret = get_config('facebook', 'appsecret');
196
197                 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id='
198                         . $appid . '&client_secret=' . $appsecret . '&redirect_uri='
199                         . urlencode($a->get_baseurl() . '/facebook/' . $nick) 
200                         . '&code=' . $auth_code);
201
202                 logger('facebook_init: returned access token: ' . $x, LOGGER_DATA);
203
204                 if(strpos($x,'access_token=') !== false) {
205                         $token = str_replace('access_token=', '', $x);
206                         if(strpos($token,'&') !== false)
207                                 $token = substr($token,0,strpos($token,'&'));
208                         set_pconfig($uid,'facebook','access_token',$token);
209                         set_pconfig($uid,'facebook','post','1');
210                         if(get_pconfig($uid,'facebook','no_linking') === false)
211                                 set_pconfig($uid,'facebook','no_linking',1);
212                         fb_get_self($uid);
213                         fb_get_friends($uid, true);
214                         fb_consume_all($uid);
215
216                 }
217
218         }
219
220 }
221
222
223 function fb_get_self($uid) {
224         $access_token = get_pconfig($uid,'facebook','access_token');
225         if(! $access_token)
226                 return;
227         $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token);
228         if($s) {
229                 $j = json_decode($s);
230                 set_pconfig($uid,'facebook','self_id',(string) $j->id);
231         }
232 }
233
234 function fb_get_friends_sync_new($uid, $access_token, $person) {
235         $link = 'http://facebook.com/profile.php?id=' . $person->id;
236         
237         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
238                 intval($uid),
239                 dbesc($link)
240         );
241         
242         if (count($r) == 0) {
243                 logger('fb_get_friends: new contact found: ' . $link, LOGGER_DEBUG);
244                 
245                 fb_get_friends_sync_full($uid, $access_token, $person);
246         }
247 }
248
249 function fb_get_friends_sync_full($uid, $access_token, $person) {
250         $s = fetch_url('https://graph.facebook.com/' . $person->id . '?access_token=' . $access_token);
251         if($s) {
252                 $jp = json_decode($s);
253                 logger('fb_get_friends: info: ' . print_r($jp,true), LOGGER_DATA);
254
255                 // always use numeric link for consistency
256
257                 $jp->link = 'http://facebook.com/profile.php?id=' . $person->id;
258
259                 // check if we already have a contact
260
261                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
262                         intval($uid),
263                         dbesc($jp->link)
264                 );                      
265
266                 if(count($r)) {
267
268                         // check that we have all the photos, this has been known to fail on occasion
269
270                         if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro'])) {  
271                                 require_once("Photo.php");
272
273                                 $photos = import_profile_photo('https://graph.facebook.com/' . $jp->id . '/picture', $uid, $r[0]['id']);
274
275                                 $r = q("UPDATE `contact` SET `photo` = '%s', 
276                                         `thumb` = '%s',
277                                         `micro` = '%s', 
278                                         `name-date` = '%s', 
279                                         `uri-date` = '%s', 
280                                         `avatar-date` = '%s'
281                                         WHERE `id` = %d LIMIT 1
282                                 ",
283                                         dbesc($photos[0]),
284                                         dbesc($photos[1]),
285                                         dbesc($photos[2]),
286                                         dbesc(datetime_convert()),
287                                         dbesc(datetime_convert()),
288                                         dbesc(datetime_convert()),
289                                         intval($r[0]['id'])
290                                 );                      
291                         }       
292                         return;
293                 }
294                 else {
295
296                         // create contact record 
297                         $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, 
298                                 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
299                                 `writable`, `blocked`, `readonly`, `pending` )
300                                 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ",
301                                 intval($uid),
302                                 dbesc(datetime_convert()),
303                                 dbesc($jp->link),
304                                 dbesc(normalise_link($jp->link)),
305                                 dbesc(''),
306                                 dbesc(''),
307                                 dbesc($jp->id),
308                                 dbesc('facebook ' . $jp->id),
309                                 dbesc($jp->name),
310                                 dbesc(($jp->nickname) ? $jp->nickname : strtolower($jp->first_name)),
311                                 dbesc('https://graph.facebook.com/' . $jp->id . '/picture'),
312                                 dbesc(NETWORK_FACEBOOK),
313                                 intval(CONTACT_IS_FRIEND),
314                                 intval(1),
315                                 intval(1)
316                         );
317                 }
318
319                 $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1",
320                         dbesc($jp->link),
321                         intval($uid)
322                 );
323
324                 if(! count($r)) {
325                         return;
326                 }
327
328                 $contact = $r[0];
329                 $contact_id  = $r[0]['id'];
330
331                 require_once("Photo.php");
332
333                 $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id);
334
335                 $r = q("UPDATE `contact` SET `photo` = '%s', 
336                         `thumb` = '%s',
337                         `micro` = '%s', 
338                         `name-date` = '%s', 
339                         `uri-date` = '%s', 
340                         `avatar-date` = '%s'
341                         WHERE `id` = %d LIMIT 1
342                 ",
343                         dbesc($photos[0]),
344                         dbesc($photos[1]),
345                         dbesc($photos[2]),
346                         dbesc(datetime_convert()),
347                         dbesc(datetime_convert()),
348                         dbesc(datetime_convert()),
349                         intval($contact_id)
350                 );                      
351
352         }
353 }
354
355 // if $fullsync is true, only new contacts are searched for
356
357 function fb_get_friends($uid, $fullsync = true) {
358
359         $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
360                 intval($uid)
361         );
362         if(! count($r))
363                 return;
364
365         $access_token = get_pconfig($uid,'facebook','access_token');
366
367         $no_linking = get_pconfig($uid,'facebook','no_linking');
368         if($no_linking)
369                 return;
370
371         if(! $access_token)
372                 return;
373         $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token);
374         if($s) {
375                 logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA);
376                 $j = json_decode($s);
377                 logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA);
378                 if(! $j->data)
379                         return;
380                 foreach($j->data as $person)
381                         if ($fullsync)
382                                 fb_get_friends_sync_full($uid, $access_token, $person);
383                         else
384                                 fb_get_friends_sync_new($uid, $access_token, $person);
385         }
386 }
387
388 // This is the POST method to the facebook settings page
389 // Content is posted to Facebook in the function facebook_post_hook() 
390
391 function facebook_post(&$a) {
392
393         $uid = local_user();
394         if($uid){
395
396                 $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0);
397                 set_pconfig($uid,'facebook','post_by_default', $value);
398
399                 $no_linking = get_pconfig($uid,'facebook','no_linking');
400
401                 $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0);
402                 set_pconfig($uid,'facebook','no_wall',$no_wall);
403
404                 $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0);
405                 set_pconfig($uid,'facebook','private_wall',$private_wall);
406         
407
408                 set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps'])));
409
410                 $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0);
411                 set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1));
412
413                 // FB linkage was allowed but has just been turned off - remove all FB contacts and posts
414
415                 if((! intval($no_linking)) && (! intval($linkvalue))) {
416                         $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ",
417                                 intval($uid),
418                                 dbesc(NETWORK_FACEBOOK)
419                         );
420                         if(count($r)) {
421                                 require_once('include/Contact.php');
422                                 foreach($r as $rr)
423                                         contact_remove($rr['id']);
424                         }
425                 }
426                 elseif(intval($no_linking) && intval($linkvalue)) {
427                         // FB linkage is now allowed - import stuff.
428                         fb_get_self($uid);
429                         fb_get_friends($uid, true);
430                         fb_consume_all($uid);
431                 }
432
433                 info( t('Settings updated.') . EOL);
434         } 
435
436         return;         
437 }
438
439 // Facebook settings form
440
441 function facebook_content(&$a) {
442
443         if(! local_user()) {
444                 notice( t('Permission denied.') . EOL);
445                 return '';
446         }
447
448         if($a->argc > 1 && $a->argv[1] === 'remove') {
449                 del_pconfig(local_user(),'facebook','post');
450                 info( t('Facebook disabled') . EOL);
451         }
452
453         if($a->argc > 1 && $a->argv[1] === 'friends') {
454                 fb_get_friends(local_user(), true);
455                 info( t('Updating contacts') . EOL);
456         }
457
458
459         $fb_installed = get_pconfig(local_user(),'facebook','post');
460
461         $appid = get_config('facebook','appid');
462
463         if(! $appid) {
464                 notice( t('Facebook API key is missing.') . EOL);
465                 return '';
466         }
467
468         $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' 
469                 . $a->get_baseurl() . '/addon/facebook/facebook.css' . '" media="all" />' . "\r\n";
470
471         $o .= '<h3>' . t('Facebook Connect') . '</h3>';
472
473         if(! $fb_installed) { 
474                 $o .= '<div id="facebook-enable-wrapper">';
475
476                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
477                         . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Install Facebook connector for this account.') . '</a>';
478                 $o .= '</div>';
479         }
480
481         if($fb_installed) {
482                 $o .= '<div id="facebook-disable-wrapper">';
483
484                 $o .= '<a href="' . $a->get_baseurl() . '/facebook/remove' . '">' . t('Remove Facebook connector') . '</a></div>';
485
486                 $o .= '<div id="facebook-enable-wrapper">';
487
488                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
489                         . $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>';
490                 $o .= '</div>';
491         
492                 $o .= '<div id="facebook-post-default-form">';
493                 $o .= '<form action="facebook" method="post" >';
494                 $post_by_default = get_pconfig(local_user(),'facebook','post_by_default');
495                 $checked = (($post_by_default) ? ' checked="checked" ' : '');
496                 $o .= '<input type="checkbox" name="post_by_default" value="1"' . $checked . '/>' . ' ' . t('Post to Facebook by default') . EOL;
497
498                 $no_linking = get_pconfig(local_user(),'facebook','no_linking');
499                 $checked = (($no_linking) ? '' : ' checked="checked" ');
500                 $o .= '<input type="checkbox" name="facebook_linking" value="1"' . $checked . '/>' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ;
501
502                 $o .= '<p>' . t('Facebook conversations consist of your <em>profile wall</em> and your friend <em>stream</em>.');
503                 $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.');
504                 $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '</p>';
505
506                 $private_wall = get_pconfig(local_user(),'facebook','private_wall');
507                 $checked = (($private_wall) ? ' checked="checked" ' : '');
508                 $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 ;
509
510
511                 $no_wall = get_pconfig(local_user(),'facebook','no_wall');
512                 $checked = (($no_wall) ? ' checked="checked" ' : '');
513                 $o .= '<input type="checkbox" name="facebook_no_wall" value="1"' . $checked . '/>' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ;
514
515                 $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>';
516
517
518                 $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps');
519
520                 $o .= '<div><label id="blocked-apps-label" for="blocked-apps">' . t('Comma separated applications to ignore') . ' </label></div>';
521         $o .= '<div><textarea id="blocked-apps" name="blocked_apps" >' . htmlspecialchars($blocked_apps) . '</textarea></div>';
522
523                 $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form></div>';
524         }
525
526         return $o;
527 }
528
529
530
531 function facebook_cron($a,$b) {
532
533         $last = get_config('facebook','last_poll');
534         
535         $poll_interval = intval(get_config('facebook','poll_interval'));
536         if(! $poll_interval)
537                 $poll_interval = 3600;
538
539         if($last) {
540                 $next = $last + $poll_interval;
541                 if($next > time()) 
542                         return;
543         }
544
545         logger('facebook_cron');
546
547
548         // Find the FB users on this site and randomize in case one of them
549         // uses an obscene amount of memory. It may kill this queue run
550         // but hopefully we'll get a few others through on each run. 
551
552         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() ");
553         if(count($r)) {
554                 foreach($r as $rr) {
555                         if(get_pconfig($rr['uid'],'facebook','no_linking'))
556                                 continue;
557                         $ab = intval(get_config('system','account_abandon_days'));
558                         if($ab > 0) {
559                                 $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1",
560                                         intval($rr['uid']),
561                                         intval($ab)
562                                 );
563                                 if(! count($z))
564                                         continue;
565                         }
566
567                         // check for new friends once a day
568                         $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check');
569                         if($last_friend_check) 
570                                 $next_friend_check = $last_friend_check + 86400;
571                         if($next_friend_check <= time()) {
572                                 fb_get_friends($rr['uid'], true);
573                                 set_pconfig($rr['uid'],'facebook','friend_check',time());
574                         }
575                         fb_consume_all($rr['uid']);
576                 }
577         }
578         
579         if (get_config('facebook', 'realtime_active') == 1) {
580                 if (!facebook_check_realtime_active()) {
581                         
582                         logger('facebook_cron: Facebook is not sending Real-Time Updates any more, although it is supposed to. Trying to fix it...', LOGGER_NORMAL);
583                         facebook_subscription_add_users();
584                         
585                         if (facebook_check_realtime_active()) 
586                                 logger('facebook_cron: Successful', LOGGER_NORMAL);
587                         else {
588                                 logger('facebook_cron: Failed', LOGGER_NORMAL);
589                                 
590                                 if(strlen($a->config['admin_email']) && !get_config('facebook', 'realtime_err_mailsent')) {
591                                         $res = mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'), 
592                                                 "Hi!\n\nThere's a problem with the Facebook Real-Time Updates that cannob be solved automatically. Maybe an permission issue?\n\nThis e-mail will only be sent once.",
593                                                 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n"
594                                                 . 'Content-type: text/plain; charset=UTF-8' . "\n"
595                                                 . 'Content-transfer-encoding: 8bit'
596                                         );
597                                         
598                                         set_config('facebook', 'realtime_err_mailsent', 1);
599                                 }
600                         }
601                 } else { // !facebook_check_realtime_active()
602                         del_config('facebook', 'realtime_err_mailsent');
603                 }
604         }
605         
606         set_config('facebook','last_poll', time());
607
608 }
609
610
611
612 function facebook_plugin_settings(&$a,&$b) {
613
614         $b .= '<div class="settings-block">';
615         $b .= '<h3>' . t('Facebook') . '</h3>';
616         $b .= '<a href="facebook">' . t('Facebook Connector Settings') . '</a><br />';
617         $b .= '</div>';
618
619 }
620
621
622 function facebook_plugin_admin(&$a, &$o){
623         
624         $activated = facebook_check_realtime_active();
625         if ($activated) {
626                 $o = t('Real-Time Updates are activated.') . '<br><br>';
627                 $o .= '<input type="submit" name="real_time_deactivate" value="' . t('Deactivate Real-Time Updates') . '">';
628         } else {
629                 $o = t('Real-Time Updates not activated.') . '<br><input type="submit" name="real_time_activate" value="' . t('Activate Real-Time Updates') . '">';
630         }
631 }
632
633 function facebook_plugin_admin_post(&$a, &$o){
634         if (x($_REQUEST,'real_time_activate')) {
635                 facebook_subscription_add_users();
636         }
637         if (x($_REQUEST,'real_time_deactivate')) {
638                 facebook_subscription_del_users();
639         }
640 }
641
642 function facebook_jot_nets(&$a,&$b) {
643         if(! local_user())
644                 return;
645
646         $fb_post = get_pconfig(local_user(),'facebook','post');
647         if(intval($fb_post) == 1) {
648                 $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default');
649                 $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : '');
650                 $b .= '<div class="profile-jot-net"><input type="checkbox" name="facebook_enable"' . $selected . ' value="1" /> ' 
651                         . t('Post to Facebook') . '</div>';     
652         }
653 }
654
655
656 function facebook_post_hook(&$a,&$b) {
657
658
659         if($b['deleted'] || ($b['created'] !== $b['edited']))
660                 return;
661
662         /**
663          * Post to Facebook stream
664          */
665
666         require_once('include/group.php');
667         require_once('include/html2plain.php');
668
669         logger('Facebook post');
670
671         $reply = false;
672         $likes = false;
673
674         $toplevel = (($b['id'] == $b['parent']) ? true : false);
675
676
677         $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1);
678
679         if((! $toplevel) && ($linking)) {
680                 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
681                         intval($b['parent']),
682                         intval($b['uid'])
683                 );
684                 if(count($r) && substr($r[0]['uri'],0,4) === 'fb::')
685                         $reply = substr($r[0]['uri'],4);
686                 elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::')
687                         $reply = substr($r[0]['extid'],4);
688                 else
689                         return;
690
691                 $u = q("SELECT * FROM user where uid = %d limit 1",
692                         intval($b['uid'])
693                 );
694                 if(! count($u))
695                         return;
696
697                 // only accept comments from the item owner. Other contacts are unknown to FB.
698  
699                 if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname']))
700                         return;
701                 
702
703                 logger('facebook reply id=' . $reply);
704         }
705
706         if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) {
707
708                 if($b['private'] && $reply === false) {
709                         $allow_people = expand_acl($b['allow_cid']);
710                         $allow_groups = expand_groups(expand_acl($b['allow_gid']));
711                         $deny_people  = expand_acl($b['deny_cid']);
712                         $deny_groups  = expand_groups(expand_acl($b['deny_gid']));
713
714                         $recipients = array_unique(array_merge($allow_people,$allow_groups));
715                         $deny = array_unique(array_merge($deny_people,$deny_groups));
716
717                         $allow_str = dbesc(implode(', ',$recipients));
718                         if($allow_str) {
719                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'"); 
720                                 $allow_arr = array();
721                                 if(count($r)) 
722                                         foreach($r as $rr)
723                                                 $allow_arr[] = $rr['notify'];
724                         }
725
726                         $deny_str = dbesc(implode(', ',$deny));
727                         if($deny_str) {
728                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'"); 
729                                 $deny_arr = array();
730                                 if(count($r)) 
731                                         foreach($r as $rr)
732                                                 $deny_arr[] = $rr['notify'];
733                         }
734
735                         if(count($deny_arr) && (! count($allow_arr))) {
736
737                                 // One or more FB folks were denied access but nobody on FB was specifically allowed access.
738                                 // This might cause the post to be open to public on Facebook, but only to selected members
739                                 // on another network. Since this could potentially leak a post to somebody who was denied, 
740                                 // we will skip posting it to Facebook with a slightly vague but relevant message that will 
741                                 // hopefully lead somebody to this code comment for a better explanation of what went wrong.
742
743                                 notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL);
744                                 return;
745                         }
746
747
748                         // if it's a private message but no Facebook members are allowed or denied, skip Facebook post
749
750                         if((! count($allow_arr)) && (! count($deny_arr)))
751                                 return;
752                 }
753
754                 if($b['verb'] == ACTIVITY_LIKE)
755                         $likes = true;                          
756
757
758                 $appid  = get_config('facebook', 'appid'  );
759                 $secret = get_config('facebook', 'appsecret' );
760
761                 if($appid && $secret) {
762
763                         logger('facebook: have appid+secret');
764
765                         $fb_token  = get_pconfig($b['uid'],'facebook','access_token');
766
767
768                         // post to facebook if it's a public post and we've ticked the 'post to Facebook' box, 
769                         // or it's a private message with facebook participants
770                         // or it's a reply or likes action to an existing facebook post                 
771
772                         if($fb_token && ($toplevel || $b['private'] || $reply)) {
773                                 logger('facebook: able to post');
774                                 require_once('library/facebook.php');
775                                 require_once('include/bbcode.php');     
776
777                                 $msg = $b['body'];
778
779                                 logger('Facebook post: original msg=' . $msg, LOGGER_DATA);
780
781                                 // make links readable before we strip the code
782
783                                 // unless it's a dislike - just send the text as a comment
784
785                                 if($b['verb'] == ACTIVITY_DISLIKE)
786                                         $msg = trim(strip_tags(bbcode($msg)));
787
788                                 /*$search_str = $a->get_baseurl() . '/search';
789
790                                 if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
791
792                                         // don't use hashtags for message link
793
794                                         if(strpos($matches[2],$search_str) === false) {
795                                                 $link = $matches[1];
796                                                 if(substr($matches[2],0,5) != '[img]')
797                                                         $linkname = $matches[2];
798                                         }
799                                 }
800
801                                 // strip tag links to avoid link clutter, this really should be 
802                                 // configurable because we're losing information
803
804                                 $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
805
806                                 // provide the link separately for normal links
807                                 $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
808
809                                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
810                                         $image = $matches[1];
811
812                                 $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
813
814                                 if((strpos($link,z_root()) !== false) && (! $image))
815                                         $image = $a->get_baseurl() . '/images/friendica-64.jpg';
816
817                                 $msg = trim(strip_tags(bbcode($msg)));*/
818
819                                 // Test
820
821                                 // Looking for images
822                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches))
823                                         $image = $matches[3];
824
825                                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches))
826                                         $image = $matches[1];
827
828                                 $html = bbcode($b['body']);
829                                 $msg = trim($b['title']." \n".html2plain($html, 0, true));
830                                 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
831
832                                 $toolong = false;
833
834                                 // add any attachments as text urls
835
836                                 $arr = explode(',',$b['attach']);
837
838                                 if(count($arr)) {
839                                         $msg .= "\n";
840                                         foreach($arr as $r) {
841                                                 $matches = false;
842                                                 $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
843                                                 if($cnt) {
844                                                         $msg .= "\n".$matches[1];
845                                                 }
846                                         }
847                                 }
848
849                                 // To-Do: look for bookmark-bbcode and handle it with priority
850
851                                 $links = collecturls($html);
852                                 if (sizeof($links) > 0) {
853                                         reset($links);
854                                         $link = current($links);
855                                         /*if (strlen($msg."\n".$link) <= FACEBOOK_MAXPOSTLEN)
856                                                 $msg .= "\n".$link;
857                                         else
858                                                 $toolong = true;*/
859                                 }
860
861                                 if ((strlen($msg) > FACEBOOK_MAXPOSTLEN) or $toolong) {
862                                         $shortlink = "";
863                                         require_once('library/slinky.php');
864
865                                         $display_url = $b['plink'];
866
867                                         $slinky = new Slinky( $display_url );
868                                         // setup a cascade of shortening services
869                                         // try to get a short link from these services
870                                         // in the order ur1.ca, trim, id.gd, tinyurl
871                                         $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
872                                         $shortlink = $slinky->short();
873                                         // the new message will be shortened such that "... $shortlink"
874                                         // will fit into the character limit
875                                         $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
876                                         $msg .= '... ' . $shortlink;
877                                 }
878                                 if(! strlen($msg))
879                                         return;
880
881                                 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
882
883                                 if($likes) { 
884                                         $postvars = array('access_token' => $fb_token);
885                                 }
886                                 else {
887                                         $postvars = array(
888                                                 'access_token' => $fb_token, 
889                                                 'message' => $msg
890                                         );
891                                         if(isset($image))
892                                                 $postvars['picture'] = $image;
893                                         if(isset($link))
894                                                 $postvars['link'] = $link;
895                                         if(isset($linkname))
896                                                 $postvars['name'] = $linkname;
897                                 }
898
899                                 if(($b['private']) && ($toplevel)) {
900                                         $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
901                                         if(count($allow_arr))
902                                                 $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
903                                         if(count($deny_arr))
904                                                 $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
905                                         $postvars['privacy'] .= '}';
906
907                                 }
908
909                                 if($reply) {
910                                         $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
911                                 }
912                                 else { 
913                                         $url = 'https://graph.facebook.com/me/feed';
914                                         if($b['plink'])
915                                                 $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' .  $b['plink'] . '"}';
916                                 }
917
918                                 logger('facebook: post to ' . $url);
919                                 logger('facebook: postvars: ' . print_r($postvars,true));
920
921                                 // "test_mode" prevents anything from actually being posted.
922                                 // Otherwise, let's do it.
923
924                                 if(! get_config('facebook','test_mode')) {
925                                         $x = post_url($url, $postvars);
926
927                                         $retj = json_decode($x);
928                                         if($retj->id) {
929                                                 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
930                                                         dbesc('fb::' . $retj->id),
931                                                         intval($b['id'])
932                                                 );
933                                         }
934                                         else {
935                                                 if(! $likes) {
936                                                         $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
937                                                         require_once('include/queue_fn.php');
938                                                         add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
939                                                         notice( t('Facebook post failed. Queued for retry.') . EOL);
940                                                 }
941                                         }
942                                         
943                                         logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
944                                 }
945                         }
946                 }
947         }
948 }
949
950
951 function facebook_post_local(&$a,&$b) {
952
953         // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
954         // where we will discover it during background delivery.
955
956         // This can only be triggered by a local user posting to their own wall.
957
958         if((local_user()) && (local_user() == $b['uid'])) {
959
960                 $fb_post   = intval(get_pconfig(local_user(),'facebook','post'));
961                 $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
962
963                 // if API is used, default to the chosen settings
964                 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
965                         $fb_enable = 1;
966
967                 if(! $fb_enable)
968                         return;
969
970                 if(strlen($b['postopts']))
971                         $b['postopts'] .= ',';
972                 $b['postopts'] .= 'facebook';
973         }
974 }
975
976
977 function fb_queue_hook(&$a,&$b) {
978
979         $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
980                 dbesc(NETWORK_FACEBOOK)
981         );
982         if(! count($qi))
983                 return;
984
985         require_once('include/queue_fn.php');
986
987         foreach($qi as $x) {
988                 if($x['network'] !== NETWORK_FACEBOOK)
989                         continue;
990
991                 logger('facebook_queue: run');
992
993                 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid` 
994                         WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
995                         intval($x['cid'])
996                 );
997                 if(! count($r))
998                         continue;
999
1000                 $user = $r[0];
1001
1002                 $appid  = get_config('facebook', 'appid'  );
1003                 $secret = get_config('facebook', 'appsecret' );
1004
1005                 if($appid && $secret) {
1006                         $fb_post   = intval(get_pconfig($user['uid'],'facebook','post'));
1007                         $fb_token  = get_pconfig($user['uid'],'facebook','access_token');
1008
1009                         if($fb_post && $fb_token) {
1010                                 logger('facebook_queue: able to post');
1011                                 require_once('library/facebook.php');
1012
1013                                 $z = unserialize($x['content']);
1014                                 $item = $z['item'];
1015                                 $j = post_url($z['url'],$z['post']);
1016
1017                                 $retj = json_decode($j);
1018                                 if($retj->id) {
1019                                         q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
1020                                                 dbesc('fb::' . $retj->id),
1021                                                 intval($item)
1022                                         );
1023                                         logger('facebook_queue: success: ' . $j); 
1024                                         remove_queue_item($x['id']);
1025                                 }
1026                                 else {
1027                                         logger('facebook_queue: failed: ' . $j);
1028                                         update_queue_time($x['id']);
1029                                 }
1030                         }
1031                 }
1032         }
1033 }
1034
1035 function fb_consume_all($uid) {
1036
1037         require_once('include/items.php');
1038
1039         $access_token = get_pconfig($uid,'facebook','access_token');
1040         if(! $access_token)
1041                 return;
1042         
1043         if(! get_pconfig($uid,'facebook','no_wall')) {
1044                 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
1045                 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
1046                 if($s) {
1047                         $j = json_decode($s);
1048                         logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
1049                         fb_consume_stream($uid,$j,($private_wall) ? false : true);
1050                 }
1051         }
1052         $s = fetch_url('https://graph.facebook.com/me/home?access_token=' . $access_token);
1053         if($s) {
1054                 $j = json_decode($s);
1055                 logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
1056                 fb_consume_stream($uid,$j,false);
1057         }
1058
1059 }
1060
1061 function fb_get_photo($uid,$link) {
1062         $access_token = get_pconfig($uid,'facebook','access_token');
1063         if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
1064                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1065         $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
1066         if($ret)
1067                 $photo_id = $match[1];
1068         $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
1069         $j = json_decode($x);
1070         if($j->picture)
1071                 return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
1072         else
1073                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1074 }
1075
1076 function fb_consume_stream($uid,$j,$wall = false) {
1077
1078         $a = get_app();
1079
1080
1081         $user = q("SELECT `nickname`, `blockwall` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
1082                 intval($uid)
1083         );
1084         if(! count($user))
1085                 return;
1086
1087         $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1088
1089         $no_linking = get_pconfig($uid,'facebook','no_linking');
1090         if($no_linking)
1091                 return;
1092
1093         $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1094                 intval($uid)
1095         );
1096
1097         $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
1098         $blocked_apps_arr = explode(',',$blocked_apps);
1099
1100         $self_id = get_pconfig($uid,'facebook','self_id');
1101         if(! count($j->data) || (! strlen($self_id)))
1102                 return;
1103
1104         foreach($j->data as $entry) {
1105                 logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
1106                 $datarray = array();
1107
1108                 $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
1109                                 dbesc('fb::' . $entry->id),
1110                                 dbesc('fb::' . $entry->id),
1111                                 intval($uid)
1112                 );
1113                 if(count($r)) {
1114                         $post_exists = true;
1115                         $orig_post = $r[0];
1116                         $top_item = $r[0]['id'];
1117                 }
1118                 else {
1119                         $post_exists = false;
1120                         $orig_post = null;
1121                 }
1122
1123                 if(! $orig_post) {
1124                         $datarray['gravity'] = 0;
1125                         $datarray['uid'] = $uid;
1126                         $datarray['wall'] = (($wall) ? 1 : 0);
1127                         $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
1128                         $from = $entry->from;
1129                         if($from->id == $self_id)
1130                                 $datarray['contact-id'] = $self[0]['id'];
1131                         else {
1132                                 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1133                                         dbesc($from->id),
1134                                         intval($uid)
1135                                 );
1136                                 if(count($r))
1137                                         $datarray['contact-id'] = $r[0]['id'];
1138                         }
1139
1140                         // don't store post if we don't have a contact
1141
1142                         if(! x($datarray,'contact-id')) {
1143                                 logger('no contact: post ignored');
1144                                 continue;
1145                         }
1146
1147                         $datarray['verb'] = ACTIVITY_POST;
1148                         if($wall) {
1149                                 $datarray['owner-name'] = $self[0]['name'];
1150                                 $datarray['owner-link'] = $self[0]['url'];
1151                                 $datarray['owner-avatar'] = $self[0]['thumb'];
1152                         }
1153                         if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
1154                                 $datarray['app'] = strip_tags($entry->application->name);
1155                         else
1156                                 $datarray['app'] = 'facebook';
1157
1158                         $found_blocked = false;
1159
1160                         if(count($blocked_apps_arr)) {
1161                                 foreach($blocked_apps_arr as $bad_appl) {
1162                                         if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
1163                                                 $found_blocked = true;
1164                                         }
1165                                 }
1166                         }
1167                                 
1168                         if($found_blocked) {
1169                                 logger('facebook: blocking application: ' . $datarray['app']);
1170                                 continue;
1171                         }
1172
1173                         $datarray['author-name'] = $from->name;
1174                         $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
1175                         $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
1176                         $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
1177
1178                         $datarray['body'] = escape_tags($entry->message);
1179
1180                         if($entry->picture && $entry->link) {
1181                                 $datarray['body'] .= "\n\n" . '[url=' . $entry->link . '][img]' . $entry->picture . '[/img][/url]';
1182                         }
1183                         else {
1184                                 if($entry->picture)
1185                                         $datarray['body'] .= "\n\n" . '[img]' . $entry->picture . '[/img]';
1186                                 // if just a link, it may be a wall photo - check
1187                                 if($entry->link)
1188                                         $datarray['body'] .= fb_get_photo($uid,$entry->link);
1189                         }
1190                         if($entry->name)
1191                                 $datarray['body'] .= "\n" . $entry->name;
1192                         if($entry->caption)
1193                                 $datarray['body'] .= "\n" . $entry->caption;
1194                         if($entry->description)
1195                                 $datarray['body'] .= "\n" . $entry->description;
1196                         $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1197                         $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1198
1199                         // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1200                         // as the identities are from a foreign system. Mark it as private to the owner.
1201
1202                         if($entry->privacy && $entry->privacy->value !== 'EVERYONE') {
1203                                 $datarray['private'] = 1;
1204                                 $datarray['allow_cid'] = '<' . $uid . '>';
1205                         }
1206
1207                         if(trim($datarray['body']) == '') {
1208                                 logger('facebook: empty body');
1209                                 continue;
1210                         }
1211
1212                         $top_item = item_store($datarray);
1213                         $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1214                                 intval($top_item),
1215                                 intval($uid)
1216                         );
1217                         if(count($r)) {
1218                                 $orig_post = $r[0];
1219                                 logger('fb: new top level item posted');
1220                         }
1221                 }
1222
1223                 if(isset($entry->likes) && isset($entry->likes->data))
1224                         $likers = $entry->likes->data;
1225                 else
1226                         $likers = null;
1227
1228                 if(isset($entry->comments) && isset($entry->comments->data))
1229                         $comments = $entry->comments->data;
1230                 else
1231                         $comments = null;
1232
1233                 if(is_array($likers)) {
1234                         foreach($likers as $likes) {
1235
1236                                 if(! $orig_post)
1237                                         continue;
1238
1239                                 // If we posted the like locally, it will be found with our url, not the FB url.
1240
1241                                 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id); 
1242
1243                                 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' 
1244                                         AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1245                                         dbesc($orig_post['uri']),
1246                                         intval($uid),
1247                                         dbesc(ACTIVITY_LIKE),
1248                                         dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1249                                         dbesc($second_url)
1250                                 );
1251
1252                                 if(count($r))
1253                                         continue;
1254                                         
1255                                 $likedata = array();
1256                                 $likedata['parent'] = $top_item;
1257                                 $likedata['verb'] = ACTIVITY_LIKE;
1258                                 $likedata['gravity'] = 3;
1259                                 $likedata['uid'] = $uid;
1260                                 $likedata['wall'] = (($wall) ? 1 : 0);
1261                                 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1262                                 $likedata['parent-uri'] = $orig_post['uri'];
1263                                 if($likes->id == $self_id)
1264                                         $likedata['contact-id'] = $self[0]['id'];
1265                                 else {
1266                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1267                                                 dbesc($likes->id),
1268                                                 intval($uid)
1269                                         );
1270                                         if(count($r))
1271                                                 $likedata['contact-id'] = $r[0]['id'];
1272                                 }
1273                                 if(! x($likedata,'contact-id'))
1274                                         $likedata['contact-id'] = $orig_post['contact-id'];
1275
1276                                 $likedata['app'] = 'facebook';
1277                                 $likedata['verb'] = ACTIVITY_LIKE;                                              
1278                                 $likedata['author-name'] = $likes->name;
1279                                 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1280                                 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1281                                 
1282                                 $author  = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1283                                 $objauthor =  '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1284                                 $post_type = t('status');
1285                         $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1286                                 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1287
1288                                 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1289                                 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' . 
1290                                         '<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>';  
1291
1292                                 $item = item_store($likedata);                  
1293                         }
1294                 }
1295                 if(is_array($comments)) {
1296                         foreach($comments as $cmnt) {
1297
1298                                 if(! $orig_post)
1299                                         continue;
1300
1301                                 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1302                                         intval($uid),
1303                                         dbesc('fb::' . $cmnt->id),
1304                                         dbesc('fb::' . $cmnt->id)
1305                                 );
1306                                 if(count($r))
1307                                         continue;
1308
1309                                 $cmntdata = array();
1310                                 $cmntdata['parent'] = $top_item;
1311                                 $cmntdata['verb'] = ACTIVITY_POST;
1312                                 $cmntdata['gravity'] = 6;
1313                                 $cmntdata['uid'] = $uid;
1314                                 $cmntdata['wall'] = (($wall) ? 1 : 0);
1315                                 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1316                                 $cmntdata['parent-uri'] = $orig_post['uri'];
1317                                 if($cmnt->from->id == $self_id) {
1318                                         $cmntdata['contact-id'] = $self[0]['id'];
1319                                 }
1320                                 else {
1321                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1322                                                 dbesc($cmnt->from->id),
1323                                                 intval($uid)
1324                                         );
1325                                         if(count($r)) {
1326                                                 $cmntdata['contact-id'] = $r[0]['id'];
1327                                                 if($r[0]['blocked'] || $r[0]['readonly'])
1328                                                         continue;
1329                                         }
1330                                 }
1331                                 if(! x($cmntdata,'contact-id'))
1332                                         $cmntdata['contact-id'] = $orig_post['contact-id'];
1333
1334                                 $cmntdata['app'] = 'facebook';
1335                                 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1336                                 $cmntdata['edited']  = datetime_convert('UTC','UTC',$cmnt->created_time);
1337                                 $cmntdata['verb'] = ACTIVITY_POST;                                              
1338                                 $cmntdata['author-name'] = $cmnt->from->name;
1339                                 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1340                                 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1341                                 $cmntdata['body'] = $cmnt->message;
1342                                 $item = item_store($cmntdata);                  
1343                         }
1344                 }
1345         }
1346 }
1347
1348
1349 function fb_get_app_access_token() {
1350         
1351         $acc_token = get_config('facebook','app_access_token');
1352         
1353         if ($acc_token !== false) return $acc_token;
1354         
1355         $appid = get_config('facebook','appid');
1356         $appsecret = get_config('facebook', 'appsecret');
1357         
1358         if ($appid === false || $appsecret === false) {
1359                 logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG);
1360                 return false;
1361         }
1362         
1363         $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . "&grant_type=client_credentials");
1364         
1365         if(strpos($x,'access_token=') !== false) {
1366                 logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA);
1367         
1368                 $token = str_replace('access_token=', '', $x);
1369                 if(strpos($token,'&') !== false)
1370                         $token = substr($token,0,strpos($token,'&'));
1371                 
1372                 if ($token == "") {
1373                         logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG);
1374                         return false;
1375                 }
1376                 set_config('facebook','app_access_token',$token);
1377                 return $token;
1378         } else {
1379                 logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA);
1380                 return false;
1381         }
1382 }
1383
1384 function facebook_subscription_del_users() {
1385         $a = get_app();
1386         $access_token = fb_get_app_access_token();
1387         
1388         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1389         facebook_delete_url($url);
1390         
1391         del_config('facebook', 'realtime_active');
1392 }
1393
1394 function facebook_subscription_add_users() {
1395         
1396         $a = get_app();
1397         $access_token = fb_get_app_access_token();
1398         
1399         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1400         
1401         list($usec, $sec) = explode(" ", microtime());
1402         $verify_token = sha1($usec . $sec . rand(0, 999999999));
1403         set_config('facebook', 'cb_verify_token', $verify_token);
1404         
1405         $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1';
1406         
1407         $j = post_url($url,array(
1408                 "object" => "user",
1409                 "fields" => "feed,friends",
1410                 "callback_url" => $cb,
1411                 "verify_token" => $verify_token,
1412         ));
1413         del_config('facebook', 'cb_verify_token');
1414         
1415         if ($j) {
1416                 logger("Facebook reponse: " . $j, LOGGER_DATA);
1417                 
1418                 if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1);
1419         };
1420 }
1421
1422 function facebook_subscriptions_get() {
1423         
1424         $access_token = fb_get_app_access_token();
1425         if (!$access_token) return null;
1426         
1427         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1428         $j = fetch_url($url);
1429         $ret = null;
1430         if ($j) {
1431                 $x = json_decode($j);
1432                 if (isset($x->data)) $ret = $x->data;
1433         }
1434         return $ret;
1435 }
1436
1437
1438 function facebook_check_realtime_active() {
1439         $ret = facebook_subscriptions_get();
1440         if (is_null($ret)) return false;
1441         if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true;
1442         return false;
1443 }
1444
1445
1446
1447
1448 // DELETE-request to $url
1449
1450 if(! function_exists('facebook_delete_url')) {
1451 function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) {
1452         $a = get_app();
1453         $ch = curl_init($url);
1454         if(($redirects > 8) || (! $ch)) 
1455                 return false;
1456
1457         curl_setopt($ch, CURLOPT_HEADER, true);
1458         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
1459         curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
1460         curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
1461
1462         if(intval($timeout)) {
1463                 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1464         }
1465         else {
1466                 $curl_time = intval(get_config('system','curl_timeout'));
1467                 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
1468         }
1469
1470         if(defined('LIGHTTPD')) {
1471                 if(!is_array($headers)) {
1472                         $headers = array('Expect:');
1473                 } else {
1474                         if(!in_array('Expect:', $headers)) {
1475                                 array_push($headers, 'Expect:');
1476                         }
1477                 }
1478         }
1479         if($headers)
1480                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1481
1482         $check_cert = get_config('system','verifyssl');
1483         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
1484         $prx = get_config('system','proxy');
1485         if(strlen($prx)) {
1486                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
1487                 curl_setopt($ch, CURLOPT_PROXY, $prx);
1488                 $prxusr = get_config('system','proxyuser');
1489                 if(strlen($prxusr))
1490                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
1491         }
1492
1493         $a->set_curl_code(0);
1494
1495         // don't let curl abort the entire application
1496         // if it throws any errors.
1497
1498         $s = @curl_exec($ch);
1499
1500         $base = $s;
1501         $curl_info = curl_getinfo($ch);
1502         $http_code = $curl_info['http_code'];
1503
1504         $header = '';
1505
1506         // Pull out multiple headers, e.g. proxy and continuation headers
1507         // allow for HTTP/2.x without fixing code
1508
1509         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
1510                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
1511                 $header .= $chunk;
1512                 $base = substr($base,strlen($chunk));
1513         }
1514
1515         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
1516         $matches = array();
1517         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
1518         $url = trim(array_pop($matches));
1519         $url_parsed = @parse_url($url);
1520         if (isset($url_parsed)) {
1521             $redirects++;
1522             return delete_url($url,$headers,$redirects,$timeout);
1523         }
1524     }
1525         $a->set_curl_code($http_code);
1526         $body = substr($s,strlen($header));
1527
1528         $a->set_curl_headers($header);
1529
1530         curl_close($ch);
1531         return($body);
1532 }}