]> git.mxchange.org Git - friendica-addons.git/blob - facebook/facebook.php
Merge pull request #10 from annando/master
[friendica-addons.git] / facebook / facebook.php
1 <?php
2 /**
3  * Name: Facebook Connector
4  * Version: 1.0
5  * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
6  */
7
8 /**
9  * Installing the Friendica/Facebook connector
10  *
11  * 1. register an API key for your site from developer.facebook.com
12  *   a. We'd be very happy if you include "Friendica" in the application name
13  *      to increase name recognition. The Friendica icons are also present
14  *      in the images directory and may be uploaded as a Facebook app icon.
15  *      Use images/friendica-16.jpg for the Icon and images/friendica-128.jpg for the Logo.
16  *   b. The url should be your site URL with a trailing slash.
17  *      Friendica is a software application and does not require a Privacy Policy 
18  *      or Terms of Service, though your installation of it might. Facebook may require
19  *      that you provide a Privacy Policy, which we find ironic.  
20  *   c. Set the following values in your .htconfig.php file
21  *         $a->config['facebook']['appid'] = 'xxxxxxxxxxx';
22  *         $a->config['facebook']['appsecret'] = 'xxxxxxxxxxxxxxx';
23  *      Replace with the settings Facebook gives you.
24  *   d. Navigate to Set Web->Site URL & Domain -> Website Settings.  Set 
25  *      Site URL to yoursubdomain.yourdomain.com. Set Site Domain to your 
26  *      yourdomain.com.
27  * 2. (This step is now obsolete. Enable the plugin via the Admin panel.)
28  *     Enable the facebook plugin by including it in .htconfig.php - e.g. 
29  *     $a->config['system']['addon'] = 'plugin1,plugin2,facebook';
30  * 3. Visit the Facebook Settings section of the "Settings->Plugin Settings" page.
31  *    and click 'Install Facebook Connector'.
32  * 4. This will ask you to login to Facebook and grant permission to the 
33  *    plugin to do its stuff. Allow it to do so. 
34  * 5. You're done. To turn it off visit the Plugin Settings page again and
35  *    'Remove Facebook posting'.
36  *
37  * Vidoes and embeds will not be posted if there is no other content. Links 
38  * and images will be converted to a format suitable for the Facebook API and 
39  * long posts truncated - with a link to view the full post. 
40  *
41  * Facebook contacts will not be able to view private photos, as they are not able to
42  * authenticate to your site to establish identity. We will address this 
43  * in a future release.
44  */
45
46 define('FACEBOOK_MAXPOSTLEN', 420);
47
48
49 function facebook_install() {
50         register_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
51         register_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
52         register_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
53         register_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
54         register_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
55         register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
56 }
57
58
59 function facebook_uninstall() {
60         unregister_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
61         unregister_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
62         unregister_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
63         unregister_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
64         unregister_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
65         unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
66
67         // hook moved
68         unregister_hook('post_local_end',  'addon/facebook/facebook.php', 'facebook_post_hook');
69         unregister_hook('plugin_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
70 }
71
72
73 /* declare the facebook_module function so that /facebook url requests will land here */
74
75 function facebook_module() {}
76
77
78
79 /* If a->argv[1] is a nickname, this is a callback from Facebook oauth requests. */
80
81 function facebook_init(&$a) {
82
83         if($a->argc != 2)
84                 return;
85         $nick = $a->argv[1];
86         if(strlen($nick))
87                 $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1",
88                                 dbesc($nick)
89                 );
90         if(! count($r))
91                 return;
92
93         $uid           = $r[0]['uid'];
94         $auth_code     = (($_GET['code']) ? $_GET['code'] : '');
95         $error         = (($_GET['error_description']) ? $_GET['error_description'] : '');
96
97
98         if($error)
99                 logger('facebook_init: Error: ' . $error);
100
101         if($auth_code && $uid) {
102
103                 $appid = get_config('facebook','appid');
104                 $appsecret = get_config('facebook', 'appsecret');
105
106                 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id='
107                         . $appid . '&client_secret=' . $appsecret . '&redirect_uri='
108                         . urlencode($a->get_baseurl() . '/facebook/' . $nick) 
109                         . '&code=' . $auth_code);
110
111                 logger('facebook_init: returned access token: ' . $x, LOGGER_DATA);
112
113                 if(strpos($x,'access_token=') !== false) {
114                         $token = str_replace('access_token=', '', $x);
115                         if(strpos($token,'&') !== false)
116                                 $token = substr($token,0,strpos($token,'&'));
117                         set_pconfig($uid,'facebook','access_token',$token);
118                         set_pconfig($uid,'facebook','post','1');
119                         if(get_pconfig($uid,'facebook','no_linking') === false)
120                                 set_pconfig($uid,'facebook','no_linking',1);
121                         fb_get_self($uid);
122                         fb_get_friends($uid);
123                         fb_consume_all($uid);
124
125                 }
126
127         }
128
129 }
130
131
132 function fb_get_self($uid) {
133         $access_token = get_pconfig($uid,'facebook','access_token');
134         if(! $access_token)
135                 return;
136         $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token);
137         if($s) {
138                 $j = json_decode($s);
139                 set_pconfig($uid,'facebook','self_id',(string) $j->id);
140         }
141 }
142
143
144
145 function fb_get_friends($uid) {
146
147         $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
148                 intval($uid)
149         );
150         if(! count($r))
151                 return;
152
153         $access_token = get_pconfig($uid,'facebook','access_token');
154
155         $no_linking = get_pconfig($uid,'facebook','no_linking');
156         if($no_linking)
157                 return;
158
159         if(! $access_token)
160                 return;
161         $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token);
162         if($s) {
163                 logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA);
164                 $j = json_decode($s);
165                 logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA);
166                 if(! $j->data)
167                         return;
168                 foreach($j->data as $person) {
169                         $s = fetch_url('https://graph.facebook.com/' . $person->id . '?access_token=' . $access_token);
170                         if($s) {
171                                 $jp = json_decode($s);
172                                 logger('fb_get_friends: info: ' . print_r($jp,true), LOGGER_DATA);
173
174                                 // always use numeric link for consistency
175
176                                 $jp->link = 'http://facebook.com/profile.php?id=' . $person->id;
177
178                                 // check if we already have a contact
179
180                                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
181                                         intval($uid),
182                                         dbesc($jp->link)
183                                 );                      
184
185                                 if(count($r)) {
186
187                                         // check that we have all the photos, this has been known to fail on occasion
188
189                                         if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro'])) {  
190                                                 require_once("Photo.php");
191
192                                                 $photos = import_profile_photo('https://graph.facebook.com/' . $jp->id . '/picture', $uid, $r[0]['id']);
193
194                                                 $r = q("UPDATE `contact` SET `photo` = '%s', 
195                                                         `thumb` = '%s',
196                                                         `micro` = '%s', 
197                                                         `name-date` = '%s', 
198                                                         `uri-date` = '%s', 
199                                                         `avatar-date` = '%s'
200                                                         WHERE `id` = %d LIMIT 1
201                                                 ",
202                                                         dbesc($photos[0]),
203                                                         dbesc($photos[1]),
204                                                         dbesc($photos[2]),
205                                                         dbesc(datetime_convert()),
206                                                         dbesc(datetime_convert()),
207                                                         dbesc(datetime_convert()),
208                                                         intval($r[0]['id'])
209                                                 );                      
210                                         }       
211                                         continue;
212                                 }
213                                 else {
214
215                                         // create contact record 
216                                         $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, 
217                                                 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
218                                                 `writable`, `blocked`, `readonly`, `pending` )
219                                                 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ",
220                                                 intval($uid),
221                                                 dbesc(datetime_convert()),
222                                                 dbesc($jp->link),
223                                                 dbesc(normalise_link($jp->link)),
224                                                 dbesc(''),
225                                                 dbesc(''),
226                                                 dbesc($jp->id),
227                                                 dbesc('facebook ' . $jp->id),
228                                                 dbesc($jp->name),
229                                                 dbesc(($jp->nickname) ? $jp->nickname : strtolower($jp->first_name)),
230                                                 dbesc('https://graph.facebook.com/' . $jp->id . '/picture'),
231                                                 dbesc(NETWORK_FACEBOOK),
232                                                 intval(CONTACT_IS_FRIEND),
233                                                 intval(1),
234                                                 intval(1)
235                                         );
236                                 }
237
238                                 $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1",
239                                         dbesc($jp->link),
240                                         intval($uid)
241                                 );
242
243                                 if(! count($r)) {
244                                         continue;
245                                 }
246
247                                 $contact = $r[0];
248                                 $contact_id  = $r[0]['id'];
249
250                                 require_once("Photo.php");
251
252                                 $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id);
253
254                                 $r = q("UPDATE `contact` SET `photo` = '%s', 
255                                         `thumb` = '%s',
256                                         `micro` = '%s', 
257                                         `name-date` = '%s', 
258                                         `uri-date` = '%s', 
259                                         `avatar-date` = '%s'
260                                         WHERE `id` = %d LIMIT 1
261                                 ",
262                                         dbesc($photos[0]),
263                                         dbesc($photos[1]),
264                                         dbesc($photos[2]),
265                                         dbesc(datetime_convert()),
266                                         dbesc(datetime_convert()),
267                                         dbesc(datetime_convert()),
268                                         intval($contact_id)
269                                 );                      
270
271                         }
272                 }
273         }
274 }
275
276 // This is the POST method to the facebook settings page
277 // Content is posted to Facebook in the function facebook_post_hook() 
278
279 function facebook_post(&$a) {
280
281         $uid = local_user();
282         if($uid){
283
284                 $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0);
285                 set_pconfig($uid,'facebook','post_by_default', $value);
286
287                 $no_linking = get_pconfig($uid,'facebook','no_linking');
288
289                 $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0);
290                 set_pconfig($uid,'facebook','no_wall',$no_wall);
291
292                 $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0);
293                 set_pconfig($uid,'facebook','private_wall',$private_wall);
294         
295
296                 set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps'])));
297
298                 $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0);
299                 set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1));
300
301                 // FB linkage was allowed but has just been turned off - remove all FB contacts and posts
302
303                 if((! intval($no_linking)) && (! intval($linkvalue))) {
304                         $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ",
305                                 intval($uid),
306                                 dbesc(NETWORK_FACEBOOK)
307                         );
308                         if(count($r)) {
309                                 require_once('include/Contact.php');
310                                 foreach($r as $rr)
311                                         contact_remove($rr['id']);
312                         }
313                 }
314                 elseif(intval($no_linking) && intval($linkvalue)) {
315                         // FB linkage is now allowed - import stuff.
316                         fb_get_self($uid);
317                         fb_get_friends($uid);
318                         fb_consume_all($uid);
319                 }
320
321                 info( t('Settings updated.') . EOL);
322         } 
323
324         return;         
325 }
326
327 // Facebook settings form
328
329 function facebook_content(&$a) {
330
331         if(! local_user()) {
332                 notice( t('Permission denied.') . EOL);
333                 return '';
334         }
335
336         if($a->argc > 1 && $a->argv[1] === 'remove') {
337                 del_pconfig(local_user(),'facebook','post');
338                 info( t('Facebook disabled') . EOL);
339         }
340
341         if($a->argc > 1 && $a->argv[1] === 'friends') {
342                 fb_get_friends(local_user());
343                 info( t('Updating contacts') . EOL);
344         }
345
346
347         $fb_installed = get_pconfig(local_user(),'facebook','post');
348
349         $appid = get_config('facebook','appid');
350
351         if(! $appid) {
352                 notice( t('Facebook API key is missing.') . EOL);
353                 return '';
354         }
355
356         $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' 
357                 . $a->get_baseurl() . '/addon/facebook/facebook.css' . '" media="all" />' . "\r\n";
358
359         $o .= '<h3>' . t('Facebook Connect') . '</h3>';
360
361         if(! $fb_installed) { 
362                 $o .= '<div id="facebook-enable-wrapper">';
363
364                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
365                         . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Install Facebook connector for this account.') . '</a>';
366                 $o .= '</div>';
367         }
368
369         if($fb_installed) {
370                 $o .= '<div id="facebook-disable-wrapper">';
371
372                 $o .= '<a href="' . $a->get_baseurl() . '/facebook/remove' . '">' . t('Remove Facebook connector') . '</a></div>';
373
374                 $o .= '<div id="facebook-enable-wrapper">';
375
376                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
377                         . $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>';
378                 $o .= '</div>';
379         
380                 $o .= '<div id="facebook-post-default-form">';
381                 $o .= '<form action="facebook" method="post" >';
382                 $post_by_default = get_pconfig(local_user(),'facebook','post_by_default');
383                 $checked = (($post_by_default) ? ' checked="checked" ' : '');
384                 $o .= '<input type="checkbox" name="post_by_default" value="1"' . $checked . '/>' . ' ' . t('Post to Facebook by default') . EOL;
385
386                 $no_linking = get_pconfig(local_user(),'facebook','no_linking');
387                 $checked = (($no_linking) ? '' : ' checked="checked" ');
388                 $o .= '<input type="checkbox" name="facebook_linking" value="1"' . $checked . '/>' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ;
389
390                 $o .= '<p>' . t('Facebook conversations consist of your <em>profile wall</em> and your friend <em>stream</em>.');
391                 $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.');
392                 $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '</p>';
393
394                 $private_wall = get_pconfig(local_user(),'facebook','private_wall');
395                 $checked = (($private_wall) ? ' checked="checked" ' : '');
396                 $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 ;
397
398
399                 $no_wall = get_pconfig(local_user(),'facebook','no_wall');
400                 $checked = (($no_wall) ? ' checked="checked" ' : '');
401                 $o .= '<input type="checkbox" name="facebook_no_wall" value="1"' . $checked . '/>' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ;
402
403                 $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>';
404
405
406                 $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps');
407
408                 $o .= '<div><label id="blocked-apps-label" for="blocked-apps">' . t('Comma separated applications to ignore') . ' </label></div>';
409         $o .= '<div><textarea id="blocked-apps" name="blocked_apps" >' . htmlspecialchars($blocked_apps) . '</textarea></div>';
410
411                 $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form></div>';
412         }
413
414         return $o;
415 }
416
417
418
419 function facebook_cron($a,$b) {
420
421         $last = get_config('facebook','last_poll');
422         
423         $poll_interval = intval(get_config('facebook','poll_interval'));
424         if(! $poll_interval)
425                 $poll_interval = 3600;
426
427         if($last) {
428                 $next = $last + $poll_interval;
429                 if($next > time()) 
430                         return;
431         }
432
433         logger('facebook_cron');
434
435
436         // Find the FB users on this site and randomize in case one of them
437         // uses an obscene amount of memory. It may kill this queue run
438         // but hopefully we'll get a few others through on each run. 
439
440         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() ");
441         if(count($r)) {
442                 foreach($r as $rr) {
443                         if(get_pconfig($rr['uid'],'facebook','no_linking'))
444                                 continue;
445                         $ab = intval(get_config('system','account_abandon_days'));
446                         if($ab > 0) {
447                                 $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1",
448                                         intval($rr['uid']),
449                                         intval($ab)
450                                 );
451                                 if(! count($z))
452                                         continue;
453                         }
454
455                         // check for new friends once a day
456                         $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check');
457                         if($last_friend_check) 
458                                 $next_friend_check = $last_friend_check + 86400;
459                         if($next_friend_check <= time()) {
460                                 fb_get_friends($rr['uid']);
461                                 set_pconfig($rr['uid'],'facebook','friend_check',time());
462                         }
463                         fb_consume_all($rr['uid']);
464                 }
465         }       
466
467         set_config('facebook','last_poll', time());
468
469 }
470
471
472
473 function facebook_plugin_settings(&$a,&$b) {
474
475         $b .= '<div class="settings-block">';
476         $b .= '<h3>' . t('Facebook') . '</h3>';
477         $b .= '<a href="facebook">' . t('Facebook Connector Settings') . '</a><br />';
478         $b .= '</div>';
479
480 }
481
482 function facebook_jot_nets(&$a,&$b) {
483         if(! local_user())
484                 return;
485
486         $fb_post = get_pconfig(local_user(),'facebook','post');
487         if(intval($fb_post) == 1) {
488                 $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default');
489                 $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : '');
490                 $b .= '<div class="profile-jot-net"><input type="checkbox" name="facebook_enable"' . $selected . ' value="1" /> ' 
491                         . t('Post to Facebook') . '</div>';     
492         }
493 }
494
495
496 function facebook_post_hook(&$a,&$b) {
497
498
499         if($b['deleted'] || ($b['created'] !== $b['edited']))
500                 return;
501
502         /**
503          * Post to Facebook stream
504          */
505
506         require_once('include/group.php');
507
508         logger('Facebook post');
509
510         $reply = false;
511         $likes = false;
512
513         $toplevel = (($b['id'] == $b['parent']) ? true : false);
514
515
516         $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1);
517
518         if((! $toplevel) && ($linking)) {
519                 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
520                         intval($b['parent']),
521                         intval($b['uid'])
522                 );
523                 if(count($r) && substr($r[0]['uri'],0,4) === 'fb::')
524                         $reply = substr($r[0]['uri'],4);
525                 elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::')
526                         $reply = substr($r[0]['extid'],4);
527                 else
528                         return;
529
530                 $u = q("SELECT * FROM user where uid = %d limit 1",
531                         intval($b['uid'])
532                 );
533                 if(! count($u))
534                         return;
535
536                 // only accept comments from the item owner. Other contacts are unknown to FB.
537  
538                 if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname']))
539                         return;
540                 
541
542                 logger('facebook reply id=' . $reply);
543         }
544
545         if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) {
546
547                 if($b['private'] && $reply === false) {
548                         $allow_people = expand_acl($b['allow_cid']);
549                         $allow_groups = expand_groups(expand_acl($b['allow_gid']));
550                         $deny_people  = expand_acl($b['deny_cid']);
551                         $deny_groups  = expand_groups(expand_acl($b['deny_gid']));
552
553                         $recipients = array_unique(array_merge($allow_people,$allow_groups));
554                         $deny = array_unique(array_merge($deny_people,$deny_groups));
555
556                         $allow_str = dbesc(implode(', ',$recipients));
557                         if($allow_str) {
558                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'"); 
559                                 $allow_arr = array();
560                                 if(count($r)) 
561                                         foreach($r as $rr)
562                                                 $allow_arr[] = $rr['notify'];
563                         }
564
565                         $deny_str = dbesc(implode(', ',$deny));
566                         if($deny_str) {
567                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'"); 
568                                 $deny_arr = array();
569                                 if(count($r)) 
570                                         foreach($r as $rr)
571                                                 $deny_arr[] = $rr['notify'];
572                         }
573
574                         if(count($deny_arr) && (! count($allow_arr))) {
575
576                                 // One or more FB folks were denied access but nobody on FB was specifically allowed access.
577                                 // This might cause the post to be open to public on Facebook, but only to selected members
578                                 // on another network. Since this could potentially leak a post to somebody who was denied, 
579                                 // we will skip posting it to Facebook with a slightly vague but relevant message that will 
580                                 // hopefully lead somebody to this code comment for a better explanation of what went wrong.
581
582                                 notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL);
583                                 return;
584                         }
585
586
587                         // if it's a private message but no Facebook members are allowed or denied, skip Facebook post
588
589                         if((! count($allow_arr)) && (! count($deny_arr)))
590                                 return;
591                 }
592
593                 if($b['verb'] == ACTIVITY_LIKE)
594                         $likes = true;                          
595
596
597                 $appid  = get_config('facebook', 'appid'  );
598                 $secret = get_config('facebook', 'appsecret' );
599
600                 if($appid && $secret) {
601
602                         logger('facebook: have appid+secret');
603
604                         $fb_token  = get_pconfig($b['uid'],'facebook','access_token');
605
606
607                         // post to facebook if it's a public post and we've ticked the 'post to Facebook' box, 
608                         // or it's a private message with facebook participants
609                         // or it's a reply or likes action to an existing facebook post                 
610
611                         if($fb_token && ($toplevel || $b['private'] || $reply)) {
612                                 logger('facebook: able to post');
613                                 require_once('library/facebook.php');
614                                 require_once('include/bbcode.php');     
615
616                                 $msg = $b['body'];
617
618                                 logger('Facebook post: original msg=' . $msg, LOGGER_DATA);
619
620                                 // make links readable before we strip the code
621
622                                 // unless it's a dislike - just send the text as a comment
623
624                                 if($b['verb'] == ACTIVITY_DISLIKE)
625                                         $msg = trim(strip_tags(bbcode($msg)));
626
627                                 $search_str = $a->get_baseurl() . '/search';
628
629                                 if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
630
631                                         // don't use hashtags for message link
632
633                                         if(strpos($matches[2],$search_str) === false) {
634                                                 $link = $matches[1];
635                                                 if(substr($matches[2],0,5) != '[img]')
636                                                         $linkname = $matches[2];
637                                         }
638                                 }
639
640                                 // strip tag links to avoid link clutter, this really should be 
641                                 // configurable because we're losing information
642
643                                 $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
644
645                                 // provide the link separately for normal links
646                                 $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
647
648                                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
649                                         $image = $matches[1];
650
651                                 $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
652
653                                 if((strpos($link,z_root()) !== false) && (! $image))
654                                         $image = $a->get_baseurl() . '/images/friendica-64.jpg';
655
656                                 $msg = trim(strip_tags(bbcode($msg)));
657                                 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
658
659                                 // add any attachments as text urls
660
661                             $arr = explode(',',$b['attach']);
662
663                             if(count($arr)) {
664                                         $msg .= "\n";
665                                 foreach($arr as $r) {
666                                 $matches = false;
667                                                 $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
668                                                 if($cnt) {
669                                                         $msg .= $matches[1];
670                                                 }
671                                         }
672                                 }
673
674                                 if (strlen($msg) > FACEBOOK_MAXPOSTLEN) {
675                                         $shortlink = "";
676                                         require_once('library/slinky.php');
677
678                                         $display_url = $b['plink'];
679
680                                         $slinky = new Slinky( $display_url );
681                                         // setup a cascade of shortening services
682                                         // try to get a short link from these services
683                                         // in the order ur1.ca, trim, id.gd, tinyurl
684                                         $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
685                                         $shortlink = $slinky->short();
686                                         // the new message will be shortened such that "... $shortlink"
687                                         // will fit into the character limit
688                                         $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
689                                         $msg .= '... ' . $shortlink;
690                                 }
691                                 if(! strlen($msg))
692                                         return;
693
694                                 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
695
696                                 if($likes) { 
697                                         $postvars = array('access_token' => $fb_token);
698                                 }
699                                 else {
700                                         $postvars = array(
701                                                 'access_token' => $fb_token, 
702                                                 'message' => $msg
703                                         );
704                                         if(isset($image))
705                                                 $postvars['picture'] = $image;
706                                         if(isset($link))
707                                                 $postvars['link'] = $link;
708                                         if(isset($linkname))
709                                                 $postvars['name'] = $linkname;
710                                 }
711
712                                 if(($b['private']) && ($toplevel)) {
713                                         $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
714                                         if(count($allow_arr))
715                                                 $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
716                                         if(count($deny_arr))
717                                                 $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
718                                         $postvars['privacy'] .= '}';
719
720                                 }
721
722                                 if($reply) {
723                                         $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
724                                 }
725                                 else { 
726                                         $url = 'https://graph.facebook.com/me/feed';
727                                         if($b['plink'])
728                                                 $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' .  $b['plink'] . '"}';
729                                 }
730
731                                 logger('facebook: post to ' . $url);
732                                 logger('facebook: postvars: ' . print_r($postvars,true));
733
734                                 // "test_mode" prevents anything from actually being posted.
735                                 // Otherwise, let's do it.
736
737                                 if(! get_config('facebook','test_mode')) {
738                                         $x = post_url($url, $postvars);
739
740                                         $retj = json_decode($x);
741                                         if($retj->id) {
742                                                 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
743                                                         dbesc('fb::' . $retj->id),
744                                                         intval($b['id'])
745                                                 );
746                                         }
747                                         else {
748                                                 if(! $likes) {
749                                                         $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
750                                                         require_once('include/queue_fn.php');
751                                                         add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
752                                                         notice( t('Facebook post failed. Queued for retry.') . EOL);
753                                                 }
754                                         }
755                                         
756                                         logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
757                                 }
758                         }
759                 }
760         }
761 }
762
763
764 function facebook_post_local(&$a,&$b) {
765
766         // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
767         // where we will discover it during background delivery.
768
769         // This can only be triggered by a local user posting to their own wall.
770
771         if((local_user()) && (local_user() == $b['uid'])) {
772
773                 $fb_post   = intval(get_pconfig(local_user(),'facebook','post'));
774                 $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
775
776                 // if API is used, default to the chosen settings
777                 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
778                         $fb_enable = 1;
779
780                 if(! $fb_enable)
781                         return;
782
783                 if(strlen($b['postopts']))
784                         $b['postopts'] .= ',';
785                 $b['postopts'] .= 'facebook';
786         }
787 }
788
789
790 function fb_queue_hook(&$a,&$b) {
791
792         $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
793                 dbesc(NETWORK_FACEBOOK)
794         );
795         if(! count($qi))
796                 return;
797
798         require_once('include/queue_fn.php');
799
800         foreach($qi as $x) {
801                 if($x['network'] !== NETWORK_FACEBOOK)
802                         continue;
803
804                 logger('facebook_queue: run');
805
806                 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid` 
807                         WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
808                         intval($x['cid'])
809                 );
810                 if(! count($r))
811                         continue;
812
813                 $user = $r[0];
814
815                 $appid  = get_config('facebook', 'appid'  );
816                 $secret = get_config('facebook', 'appsecret' );
817
818                 if($appid && $secret) {
819                         $fb_post   = intval(get_pconfig($user['uid'],'facebook','post'));
820                         $fb_token  = get_pconfig($user['uid'],'facebook','access_token');
821
822                         if($fb_post && $fb_token) {
823                                 logger('facebook_queue: able to post');
824                                 require_once('library/facebook.php');
825
826                                 $z = unserialize($x['content']);
827                                 $item = $z['item'];
828                                 $j = post_url($z['url'],$z['post']);
829
830                                 $retj = json_decode($j);
831                                 if($retj->id) {
832                                         q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
833                                                 dbesc('fb::' . $retj->id),
834                                                 intval($item)
835                                         );
836                                         logger('facebook_queue: success: ' . $j); 
837                                         remove_queue_item($x['id']);
838                                 }
839                                 else {
840                                         logger('facebook_queue: failed: ' . $j);
841                                         update_queue_time($x['id']);
842                                 }
843                         }
844                 }
845         }
846 }
847
848 function fb_consume_all($uid) {
849
850         require_once('include/items.php');
851
852         $access_token = get_pconfig($uid,'facebook','access_token');
853         if(! $access_token)
854                 return;
855         
856         if(! get_pconfig($uid,'facebook','no_wall')) {
857                 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
858                 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
859                 if($s) {
860                         $j = json_decode($s);
861                         logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
862                         fb_consume_stream($uid,$j,($private_wall) ? false : true);
863                 }
864         }
865         $s = fetch_url('https://graph.facebook.com/me/home?access_token=' . $access_token);
866         if($s) {
867                 $j = json_decode($s);
868                 logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
869                 fb_consume_stream($uid,$j,false);
870         }
871
872 }
873
874 function fb_get_photo($uid,$link) {
875         $access_token = get_pconfig($uid,'facebook','access_token');
876         if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
877                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
878         $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
879         if($ret)
880                 $photo_id = $match[1];
881         $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
882         $j = json_decode($x);
883         if($j->picture)
884                 return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
885         else
886                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
887 }
888
889 function fb_consume_stream($uid,$j,$wall = false) {
890
891         $a = get_app();
892
893
894         $user = q("SELECT `nickname`, `blockwall` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
895                 intval($uid)
896         );
897         if(! count($user))
898                 return;
899
900         $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
901
902         $no_linking = get_pconfig($uid,'facebook','no_linking');
903         if($no_linking)
904                 return;
905
906         $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
907                 intval($uid)
908         );
909
910         $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
911         $blocked_apps_arr = explode(',',$blocked_apps);
912
913         $self_id = get_pconfig($uid,'facebook','self_id');
914         if(! count($j->data) || (! strlen($self_id)))
915                 return;
916
917         foreach($j->data as $entry) {
918                 logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
919                 $datarray = array();
920
921                 $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
922                                 dbesc('fb::' . $entry->id),
923                                 dbesc('fb::' . $entry->id),
924                                 intval($uid)
925                 );
926                 if(count($r)) {
927                         $post_exists = true;
928                         $orig_post = $r[0];
929                         $top_item = $r[0]['id'];
930                 }
931                 else {
932                         $post_exists = false;
933                         $orig_post = null;
934                 }
935
936                 if(! $orig_post) {
937                         $datarray['gravity'] = 0;
938                         $datarray['uid'] = $uid;
939                         $datarray['wall'] = (($wall) ? 1 : 0);
940                         $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
941                         $from = $entry->from;
942                         if($from->id == $self_id)
943                                 $datarray['contact-id'] = $self[0]['id'];
944                         else {
945                                 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
946                                         dbesc($from->id),
947                                         intval($uid)
948                                 );
949                                 if(count($r))
950                                         $datarray['contact-id'] = $r[0]['id'];
951                         }
952
953                         // don't store post if we don't have a contact
954
955                         if(! x($datarray,'contact-id')) {
956                                 // Test: Workaround for page entries
957                                 $datarray['contact-id'] = $self[0]['id'];
958                                 //logger('no contact: post ignored');
959                                 //continue;
960                         }
961
962                         $datarray['verb'] = ACTIVITY_POST;
963                         if($wall) {
964                                 $datarray['owner-name'] = $self[0]['name'];
965                                 $datarray['owner-link'] = $self[0]['url'];
966                                 $datarray['owner-avatar'] = $self[0]['thumb'];
967                         }
968                         if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
969                                 $datarray['app'] = strip_tags($entry->application->name);
970                         else
971                                 $datarray['app'] = 'facebook';
972
973                         $found_blocked = false;
974
975                         if(count($blocked_apps_arr)) {
976                                 foreach($blocked_apps_arr as $bad_appl) {
977                                         if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
978                                                 $found_blocked = true;
979                                         }
980                                 }
981                         }
982                                 
983                         if($found_blocked) {
984                                 logger('facebook: blocking application: ' . $datarray['app']);
985                                 continue;
986                         }
987
988                         $datarray['author-name'] = $from->name;
989                         $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
990                         $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
991                         $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
992
993                         $datarray['body'] = escape_tags($entry->message);
994
995                         if($entry->picture && $entry->link) {
996                                 $datarray['body'] .= "\n\n" . '[url=' . $entry->link . '][img]' . $entry->picture . '[/img][/url]';
997                         }
998                         else {
999                                 if($entry->picture)
1000                                         $datarray['body'] .= "\n\n" . '[img]' . $entry->picture . '[/img]';
1001                                 // if just a link, it may be a wall photo - check
1002                                 if($entry->link)
1003                                         $datarray['body'] .= fb_get_photo($uid,$entry->link);
1004                         }
1005                         if($entry->name)
1006                                 $datarray['body'] .= "\n" . $entry->name;
1007                         if($entry->caption)
1008                                 $datarray['body'] .= "\n" . $entry->caption;
1009                         if($entry->description)
1010                                 $datarray['body'] .= "\n" . $entry->description;
1011                         $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1012                         $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1013
1014                         // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1015                         // as the identities are from a foreign system. Mark it as private to the owner.
1016
1017                         if($entry->privacy && $entry->privacy->value !== 'EVERYONE') {
1018                                 $datarray['private'] = 1;
1019                                 $datarray['allow_cid'] = '<' . $uid . '>';
1020                         }
1021                         
1022                         $top_item = item_store($datarray);
1023                         $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1024                                 intval($top_item),
1025                                 intval($uid)
1026                         );                      
1027                         if(count($r)) {
1028                                 $orig_post = $r[0];
1029                                 logger('fb: new top level item posted');
1030                         }
1031                 }
1032
1033                 if(isset($entry->likes) && isset($entry->likes->data))
1034                         $likers = $entry->likes->data;
1035                 else
1036                         $likers = null;
1037
1038                 if(isset($entry->comments) && isset($entry->comments->data))
1039                         $comments = $entry->comments->data;
1040                 else
1041                         $comments = null;
1042
1043                 if(is_array($likers)) {
1044                         foreach($likers as $likes) {
1045
1046                                 if(! $orig_post)
1047                                         continue;
1048
1049                                 // If we posted the like locally, it will be found with our url, not the FB url.
1050
1051                                 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id); 
1052
1053                                 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' 
1054                                         AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1055                                         dbesc($orig_post['uri']),
1056                                         intval($uid),
1057                                         dbesc(ACTIVITY_LIKE),
1058                                         dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1059                                         dbesc($second_url)
1060                                 );
1061
1062                                 if(count($r))
1063                                         continue;
1064                                         
1065                                 $likedata = array();
1066                                 $likedata['parent'] = $top_item;
1067                                 $likedata['verb'] = ACTIVITY_LIKE;
1068                                 $likedata['gravity'] = 3;
1069                                 $likedata['uid'] = $uid;
1070                                 $likedata['wall'] = (($wall) ? 1 : 0);
1071                                 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1072                                 $likedata['parent-uri'] = $orig_post['uri'];
1073                                 if($likes->id == $self_id)
1074                                         $likedata['contact-id'] = $self[0]['id'];
1075                                 else {
1076                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1077                                                 dbesc($likes->id),
1078                                                 intval($uid)
1079                                         );
1080                                         if(count($r))
1081                                                 $likedata['contact-id'] = $r[0]['id'];
1082                                 }
1083                                 if(! x($likedata,'contact-id'))
1084                                         $likedata['contact-id'] = $orig_post['contact-id'];
1085
1086                                 $likedata['app'] = 'facebook';
1087                                 $likedata['verb'] = ACTIVITY_LIKE;                                              
1088                                 $likedata['author-name'] = $likes->name;
1089                                 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1090                                 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1091                                 
1092                                 $author  = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1093                                 $objauthor =  '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1094                                 $post_type = t('status');
1095                         $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1096                                 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1097
1098                                 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1099                                 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' . 
1100                                         '<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>';  
1101
1102                                 $item = item_store($likedata);                  
1103                         }
1104                 }
1105                 if(is_array($comments)) {
1106                         foreach($comments as $cmnt) {
1107
1108                                 if(! $orig_post)
1109                                         continue;
1110
1111                                 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1112                                         intval($uid),
1113                                         dbesc('fb::' . $cmnt->id),
1114                                         dbesc('fb::' . $cmnt->id)
1115                                 );
1116                                 if(count($r))
1117                                         continue;
1118
1119                                 $cmntdata = array();
1120                                 $cmntdata['parent'] = $top_item;
1121                                 $cmntdata['verb'] = ACTIVITY_POST;
1122                                 $cmntdata['gravity'] = 6;
1123                                 $cmntdata['uid'] = $uid;
1124                                 $cmntdata['wall'] = (($wall) ? 1 : 0);
1125                                 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1126                                 $cmntdata['parent-uri'] = $orig_post['uri'];
1127                                 if($cmnt->from->id == $self_id) {
1128                                         $cmntdata['contact-id'] = $self[0]['id'];
1129                                 }
1130                                 else {
1131                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1132                                                 dbesc($cmnt->from->id),
1133                                                 intval($uid)
1134                                         );
1135                                         if(count($r)) {
1136                                                 $cmntdata['contact-id'] = $r[0]['id'];
1137                                                 if($r[0]['blocked'] || $r[0]['readonly'])
1138                                                         continue;
1139                                         }
1140                                 }
1141                                 if(! x($cmntdata,'contact-id'))
1142                                         $cmntdata['contact-id'] = $orig_post['contact-id'];
1143
1144                                 $cmntdata['app'] = 'facebook';
1145                                 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1146                                 $cmntdata['edited']  = datetime_convert('UTC','UTC',$cmnt->created_time);
1147                                 $cmntdata['verb'] = ACTIVITY_POST;                                              
1148                                 $cmntdata['author-name'] = $cmnt->from->name;
1149                                 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1150                                 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1151                                 $cmntdata['body'] = $cmnt->message;
1152                                 $item = item_store($cmntdata);                  
1153                         }
1154                 }
1155         }
1156 }
1157