]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Facebook/FacebookPlugin.php
Merge commit 'origin/testing' into 0.9.x
[quix0rs-gnu-social.git] / plugins / Facebook / FacebookPlugin.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * Plugin to add a StatusNet Facebook application
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  Plugin
23  * @package   StatusNet
24  * @author    Zach Copley <zach@status.net>
25  * @copyright 2009-2010 StatusNet, Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27  * @link      http://status.net/
28  */
29
30 if (!defined('STATUSNET')) {
31     exit(1);
32 }
33
34 define("FACEBOOK_CONNECT_SERVICE", 3);
35
36 require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php';
37
38 /**
39  * Facebook plugin to add a StatusNet Facebook canvas application
40  * and allow registration and authentication via Facebook Connect
41  *
42  * @category Plugin
43  * @package  StatusNet
44  * @author   Zach Copley <zach@status.net>
45  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
46  * @link     http://status.net/
47  */
48
49 class FacebookPlugin extends Plugin
50 {
51
52     const VERSION = STATUSNET_VERSION;
53
54     /**
55      * Initializer for the plugin.
56      */
57
58     function initialize()
59     {
60         // Allow the key and secret to be passed in
61         // Control panel will override
62
63         if (isset($this->apikey)) {
64             $key = common_config('facebook', 'apikey');
65             if (empty($key)) {
66                 Config::save('facebook', 'apikey', $this->apikey);
67             }
68         }
69
70         if (isset($this->secret)) {
71             $secret = common_config('facebook', 'secret');
72             if (empty($secret)) {
73                 Config::save(
74                     'facebook',
75                     'secret',
76                     $this->secret
77                 );
78             }
79         }
80     }
81
82     /**
83      * Check to see if there is an API key and secret defined
84      * for Facebook integration.
85      *
86      * @return boolean result
87      */
88
89     static function hasKeys()
90     {
91         $apiKey    = common_config('facebook', 'apikey');
92         $apiSecret = common_config('facebook', 'secret');
93
94         if (!empty($apiKey) && !empty($apiSecret)) {
95             return true;
96         }
97
98         return false;
99     }
100
101     /**
102      * Add Facebook app actions to the router table
103      *
104      * Hook for RouterInitialized event.
105      *
106      * @param Net_URL_Mapper &$m path-to-action mapper
107      *
108      * @return boolean hook return
109      */
110
111     function onStartInitializeRouter($m)
112     {
113         $m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
114
115         if (self::hasKeys()) {
116
117             // Facebook App stuff
118
119             $m->connect('facebook/app', array('action' => 'facebookhome'));
120             $m->connect('facebook/app/index.php', array('action' => 'facebookhome'));
121             $m->connect('facebook/app/settings.php',
122                         array('action' => 'facebooksettings'));
123             $m->connect('facebook/app/invite.php', array('action' => 'facebookinvite'));
124             $m->connect('facebook/app/remove', array('action' => 'facebookremove'));
125
126             // Facebook Connect stuff
127
128             $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth'));
129             $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
130             $m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
131             $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
132         }
133
134         return true;
135     }
136
137     /**
138      * Automatically load the actions and libraries used by the Facebook app
139      *
140      * @param Class $cls the class
141      *
142      * @return boolean hook return
143      *
144      */
145
146     function onAutoload($cls)
147     {
148         switch ($cls) {
149         case 'FacebookAction':
150         case 'FacebookhomeAction':
151         case 'FacebookinviteAction':
152         case 'FacebookremoveAction':
153         case 'FacebooksettingsAction':
154         case 'FacebookadminpanelAction':
155             include_once INSTALLDIR . '/plugins/Facebook/' .
156               strtolower(mb_substr($cls, 0, -6)) . '.php';
157             return false;
158         case 'FBConnectAuthAction':
159         case 'FBConnectLoginAction':
160         case 'FBConnectSettingsAction':
161         case 'FBC_XDReceiverAction':
162             include_once INSTALLDIR . '/plugins/Facebook/' .
163               mb_substr($cls, 0, -6) . '.php';
164             return false;
165         case 'FBCLoginGroupNav':
166             include_once INSTALLDIR . '/plugins/Facebook/FBCLoginGroupNav.php';
167             return false;
168         case 'FBCSettingsNav':
169             include_once INSTALLDIR . '/plugins/Facebook/FBCSettingsNav.php';
170             return false;
171         case 'FacebookQueueHandler':
172             include_once INSTALLDIR . '/plugins/Facebook/facebookqueuehandler.php';
173             return false;
174         default:
175             return true;
176         }
177     }
178
179     /**
180      * Add a Facebook tab to the admin panels
181      *
182      * @param Widget $nav Admin panel nav
183      *
184      * @return boolean hook value
185      */
186
187     function onEndAdminPanelNav($nav)
188     {
189         if (AdminPanelAction::canAdmin('facebook')) {
190
191             $action_name = $nav->action->trimmed('action');
192
193             $nav->out->menuItem(
194                 common_local_url('facebookadminpanel'),
195                 _m('Facebook'),
196                 _m('Facebook integration configuration'),
197                 $action_name == 'facebookadminpanel',
198                 'nav_facebook_admin_panel'
199             );
200         }
201
202         return true;
203     }
204
205     /**
206      * Override normal HTML output to force the content type to
207      * text/html and add in xmlns:fb
208      *
209      * @param Action $action the current action
210      *
211      * @return void
212      */
213
214     function onStartShowHTML($action)
215     {
216
217         if ($this->reqFbScripts($action)) {
218
219             // XXX: Horrible hack to make Safari, FF2, and Chrome work with
220             // Facebook Connect. These browser cannot use Facebook's
221             // DOM parsing routines unless the mime type of the page is
222             // text/html even though Facebook Connect uses XHTML.  This is
223             // A bug in Facebook Connect, and this is a temporary solution
224             // until they fix their JavaScript libs.
225
226             header('Content-Type: text/html');
227
228             $action->extraHeaders();
229
230             $action->startXML('html');
231
232             $language = $action->getLanguage();
233
234             $action->elementStart('html',
235                 array('xmlns'  => 'http://www.w3.org/1999/xhtml',
236                       'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
237                       'xml:lang' => $language,
238                       'lang'     => $language));
239
240             return false;
241
242         } else {
243
244             return true;
245         }
246     }
247
248     /**
249      * Add in the Facebook Connect JavaScript stuff
250      *
251      * Note: this script needs to appear in the <body>
252      *
253      * @param Action $action the current action
254      *
255      * @return void
256      *
257      */
258
259     function onEndShowScripts($action)
260     {
261         if ($this->reqFbScripts($action)) {
262
263             $apikey      = common_config('facebook', 'apikey');
264             $plugin_path = 'plugins/Facebook';
265
266             $login_url  = common_local_url('FBConnectAuth');
267             $logout_url = common_local_url('logout');
268
269             // XXX: Facebook says we don't need this FB_RequireFeatures(),
270             // but we actually do, for IE and Safari. Gar.
271
272             $js .= '    $(document).ready(function () {';
273             $js .= '         FB_RequireFeatures(';
274             $js .= '             ["XFBML"], function() {';
275             $js .= '                 FB.init("%1$s", "../xd_receiver.html");';
276             $js .= '             }';
277             $js .= '         );';
278             $js .= '    });';
279
280             $js .= '    function goto_login() {';
281             $js .= '        window.location = "%2$s";';
282             $js .= '    }';
283
284             // The below function alters the logout link so that it logs the user out
285             // of Facebook Connect as well as the site.  However, for some pages
286             // (FB Connect Settings) we need to output the FB Connect scripts (to
287             // show an existing FB connection even if the user isn't authenticated
288             // with Facebook connect) but NOT alter the logout link. And the only
289             // way to reliably do that is with the FB Connect .js libs.  Crazy.
290
291             $js .= '    FB.ensureInit(function() {';
292             $js .= '        FB.Connect.ifUserConnected(';
293             $js .= '            function() { ';
294             $js .= '                $(\'#nav_logout a\').attr(\'href\', \'#\');';
295             $js .= '                $(\'#nav_logout a\').click(function() {';
296             $js .= '                   FB.Connect.logoutAndRedirect(\'%3$s\');';
297             $js .= '                   return false;';
298             $js .= '                })';
299             $js .= '            },';
300             $js .= '            function() {';
301             $js .= '                return false;';
302             $js .= '            }';
303             $js .= '        );';
304             $js .= '     });';
305
306             $js = sprintf($js, $apikey, $login_url, $logout_url);
307
308             // Compress the bugger down a bit
309
310             $js = str_replace('  ', '', $js);
311
312             $action->inlineScript($js);
313         }
314
315     }
316
317     /**
318      * Add in an additional Facebook Connect script that's supposed to
319      * appear as close as possible to </body>
320      *
321      * @param Action $action the current action
322      *
323      * @return void
324      *
325      */
326
327     function onEndShowFooter($action)
328     {
329         if ($this->reqFbScripts($action)) {
330             $action->script('http://static.ak.connect.facebook.com' .
331                             '/js/api_lib/v0.4/FeatureLoader.js.php');
332         }
333     }
334
335     /**
336      * Output Facebook Connect specific CSS link
337      *
338      * @param Action $action the current action
339      *
340      * @return void
341      *
342      */
343
344     function onEndShowStatusNetStyles($action)
345     {
346         if ($this->reqFbScripts($action)) {
347             $action->cssLink('plugins/Facebook/FBConnect.css');
348         }
349     }
350
351     /**
352      * Does the Action we're plugged into require the FB Scripts?  We only
353      * want to output FB namespace, scripts, CSS, etc. on the pages that
354      * really need them.
355      *
356      * @param Action $action the current action
357      *
358      * @return boolean true
359      */
360
361     function reqFbScripts($action)
362     {
363         if (!self::hasKeys()) {
364             return false;
365         }
366
367         // If you're logged in w/FB Connect, you always need the FB stuff
368
369         $fbuid = $this->loggedIn();
370
371         if (!empty($fbuid)) {
372             return true;
373         }
374
375         // List of actions that require FB stuff
376
377         $needy = array('FBConnectLoginAction',
378                        'FBConnectauthAction',
379                        'FBConnectSettingsAction');
380
381         if (in_array(get_class($action), $needy)) {
382             return true;
383         }
384
385         return false;
386
387     }
388
389     /**
390      * Is the user currently logged in with FB Connect?
391      *
392      * @return mixed $fbuid the Facebook ID of the logged in user, or null
393      */
394
395     function loggedIn()
396     {
397         $user = common_current_user();
398
399         if (!empty($user)) {
400
401             $flink = Foreign_link::getByUserId($user->id,
402                 FACEBOOK_CONNECT_SERVICE);
403             $fbuid = 0;
404
405             if (!empty($flink)) {
406
407                 try {
408
409                     $facebook = getFacebook();
410                     $fbuid    = $facebook->get_loggedin_user();
411
412                 } catch (Exception $e) {
413                     common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
414                         'Problem getting Facebook user: ' .
415                             $e->getMessage());
416                 }
417
418                 if ($fbuid > 0) {
419                     return $fbuid;
420                 }
421             }
422         }
423
424         return null;
425     }
426
427     /**
428      * Add in a Facebook Connect avatar to the primary nav menu
429      *
430      * @param Action $action the current action
431      *
432      * @return void
433      *
434      */
435
436     function onStartPrimaryNav($action)
437     {
438         if (self::hasKeys()) {
439             $user = common_current_user();
440             if (!empty($user)) {
441
442                 $fbuid = $this->loggedIn();
443
444                 if (!empty($fbuid)) {
445
446                     /* Default FB silhouette pic for FB users who haven't
447                        uploaded a profile pic yet. */
448
449                     $silhouetteUrl =
450                         'http://static.ak.fbcdn.net/pics/q_silhouette.gif';
451
452                     $url = $this->getProfilePicURL($fbuid);
453
454                     $action->elementStart('li', array('id' => 'nav_fb'));
455
456                     $action->element('img', array('id' => 'fbc_profile-pic',
457                         'src' => (!empty($url)) ? $url : $silhouetteUrl,
458                         'alt' => 'Facebook Connect User',
459                         'width' => '16'), '');
460
461                     $iconurl =  common_path('plugins/Facebook/fbfavicon.ico');
462                     $action->element('img', array('id' => 'fb_favicon',
463                         'src' => $iconurl));
464
465                     $action->elementEnd('li');
466                 }
467             }
468         }
469
470         return true;
471     }
472
473     /*
474      * Add a login tab for Facebook Connect
475      *
476      * @param Action &action the current action
477      *
478      * @return void
479      */
480
481     function onEndLoginGroupNav(&$action)
482     {
483         if (self::hasKeys()) {
484
485             $action_name = $action->trimmed('action');
486
487             $action->menuItem(common_local_url('FBConnectLogin'),
488                                                _m('Facebook'),
489                                                _m('Login or register using Facebook'),
490                                                'FBConnectLogin' === $action_name);
491         }
492         return true;
493     }
494
495     /*
496      * Add a tab for managing Facebook Connect settings
497      *
498      * @param Action &action the current action
499      *
500      * @return void
501      */
502
503     function onEndConnectSettingsNav(&$action)
504     {
505         if (self::hasKeys()) {
506
507             $action_name = $action->trimmed('action');
508
509             $action->menuItem(common_local_url('FBConnectSettings'),
510                               _m('Facebook'),
511                               _m('Facebook Connect Settings'),
512                               $action_name === 'FBConnectSettings');
513         }
514         return true;
515     }
516
517     /**
518      * Have the logout process do some Facebook Connect cookie cleanup
519      *
520      * @param Action $action the current action
521      *
522      * @return void
523      */
524
525     function onStartLogout($action)
526     {
527         if (self::hasKeys()) {
528
529             $action->logout();
530             $fbuid = $this->loggedIn();
531
532             if (!empty($fbuid)) {
533                 try {
534                     $facebook = getFacebook();
535                     $facebook->expire_session();
536                 } catch (Exception $e) {
537                     common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
538                                'Could\'t logout of Facebook: ' .
539                                $e->getMessage());
540                 }
541             }
542         }
543         return true;
544     }
545
546     /**
547      * Get the URL of the user's Facebook avatar
548      *
549      * @param int $fbuid the Facebook user ID
550      *
551      * @return string $url the url for the user's Facebook avatar
552      */
553
554     function getProfilePicURL($fbuid)
555     {
556         $facebook = getFacebook();
557         $url      = null;
558
559         try {
560
561             $fqry = 'SELECT pic_square FROM user WHERE uid = %s';
562
563             $result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid));
564
565             if (!empty($result)) {
566                 $url = $result[0]['pic_square'];
567             }
568
569         } catch (Exception $e) {
570             common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
571                        "Facebook client failure requesting profile pic!");
572         }
573
574         return $url;
575     }
576
577     /**
578      * Add a Facebook queue item for each notice
579      *
580      * @param Notice $notice      the notice
581      * @param array  &$transports the list of transports (queues)
582      *
583      * @return boolean hook return
584      */
585
586     function onStartEnqueueNotice($notice, &$transports)
587     {
588         if (self::hasKeys()) {
589             array_push($transports, 'facebook');
590         }
591         return true;
592     }
593
594     /**
595      * Register Facebook notice queue handler
596      *
597      * @param QueueManager $manager
598      *
599      * @return boolean hook return
600      */
601     function onEndInitializeQueueManager($manager)
602     {
603         if (self::hasKeys()) {
604             $manager->connect('facebook', 'FacebookQueueHandler');
605         }
606         return true;
607     }
608
609     function onPluginVersion(&$versions)
610     {
611         $versions[] = array(
612             'name' => 'Facebook',
613             'version' => self::VERSION,
614             'author' => 'Zach Copley',
615             'homepage' => 'http://status.net/wiki/Plugin:Facebook',
616             'rawdescription' => _m(
617                 'The Facebook plugin allows you to integrate ' .
618                 'your StatusNet instance with ' .
619                 '<a href="http://facebook.com/">Facebook</a> ' .
620                 'and Facebook Connect.'
621             )
622         );
623         return true;
624     }
625
626 }